Axios - extracting http cookies and setting them as authorization headers - reactjs

I am building authentication for my application and I am using access and refresh tokens.
Upon user login, the API issues 3 things
refresh token
access token string with headers and payload
access token string with signature
These tokens are all jwt tokens.
This article discusses why access tokens should be split.
using express, I send the tokens back to the browser in my controller like so:
res.cookie(
ACCESS_TOKEN_COOKIE_HEADER_PAYLOAD,
headerAndPayload,
COOKIE_OPTIONS,
)
res.cookie(
ACCESS_TOKEN_COOKIE_SIGNATURE,
signature,
COOKIE_OPTIONS_HTTP_ONLY,
)
res.cookie(REFRESH_TOKEN_COOKIE, refreshToken, COOKIE_OPTIONS)
return res.json({ username, uid })
auth.constants.ts
export const COOKIE_OPTIONS: CookieOptions = {
secure: true,
sameSite: 'lax',
}
export const COOKIE_OPTIONS_HTTP_ONLY: CookieOptions = {
httpOnly: true,
secure: true,
sameSite: 'lax',
}
export const ACCESS_TOKEN_COOKIE_HEADER_PAYLOAD = 'access_token_header_payload'
export const ACCESS_TOKEN_COOKIE_SIGNATURE = 'access_token_signature'
export const REFRESH_TOKEN_COOKIE = 'refresh_token'
In the ui (react) I go into Chrome devtools -> application -> storage -> cookeis and I can see that they are updated everytime I login. This is the behavior I want so that's good so far.
Now when I want to send a request to my API to create something (let's say I am creating a new blog post), I want to grab those cookies and pass them as an Authorization Header.
I am following this person's suggestion except I noticed he is using store which I am guessing is some form of state. Since I am not doing that and multiple sources (source 1, source 2) point to the fact that the standard for sending tokens to the API for authentication is using Authorization header, I would like to follow that.
Currently, when I make an API request using axios, I console log the express request object and can see my tokens in cookies like so:
headers: {
host: 'localhost:3001',
connection: 'keep-alive',
'content-length': '0',
accept: 'application/json, text/plain, */*',
'user-agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/84.0.4147.105 Safari/537.36',
origin: 'http://localhost:3000',
'sec-fetch-site': 'same-site',
'sec-fetch-mode': 'cors',
'sec-fetch-dest': 'empty',
referer: 'http://localhost:3000/',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'en-US,en;q=0.9',
cookie: 'access_token_header_payload=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvc3R5cG9vIiwiaWF0IjoxNTk2ODM0MDIwLCJleHAiOjE1OTY4MzQwODB9; access_token_signature=3pUbxjWgly9xmYSJObOvTgps9qwjOIrHWWE4LPYidmQ; refresh_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InJvc3R5cG9vIiwiaWF0IjoxNTk2ODM0MDIwLCJleHAiOjE1OTc0Mzg4MjB9.IKdRsaTTgAeUfwicLcBpRvw89WgYXy_rCRN5o2BJFqY'
},
but I want to send these cookies as Authorization: Bearer <tokens> instead. How would I do that in axios? Or is what I am doing secure?
this is my axios interceptor
import axios from 'axios'
const service = axios.create({
withCredentials: true,
baseURL: process.env.REACT_APP_API_BASE_URL,
timeout: 5000,
})
// Request interceptors
service.interceptors.request.use(
config => {
return config
},
error => {
return Promise.reject(error)
},
)
// Response interceptors
service.interceptors.response.use(
response => {
console.log('response', response)
return response.data
},
error => {
return Promise.reject({ ...error })
},
)
export default service

HttpOnly means the client script can't access the cookie, as well as you can't read it from document.cookie and pass to axios.
In fact, HttpOnly cookie is more secure than http request headers I think. What you need is parsing the auth cookie in the server side, instead of parsing the request header.

As stated in this response by chinesedfan. The way to authorize your requests to your backend API is through query parsing since your cookie is HttpOnly and can't be accessed by any client.
With express, this can be done by creating a global middleware that sets your authorization header. The following snippet shows how to do this, assuming you are using Bearer <accessToken>.
// global middleware for setting authorization header
app.use((req, res, next) => {
const authHeader = req.cookies.accessToken;
if (authHeader) {
req.headers.authorization = `Bearer ${authHeader}`;
}
next();
});
// initialize passportjs
app.use(passport.initialize())
Add this middleware in your server.js, assuming you named your initializing file this way. This is where you declare your express app variable.
In express, middleware order matters, so add this middleware before you initialize your passport middleware.
In your frontend, you don't have to add anything to axios, just make the request to the backend, and if that request needs authorization, it will be added to you automatically.

Related

Client does not receive cookies from server, postman does

There is a server, which serves my client react app at root path. So when I make any request to server from POSTMAN, to login for example, cookies are attached perfect. But when I make request from my client using AXIOS and withCredentials field as well, cookies ain't attached, nevertheless the request is sent good, but no cookies received. I don't think there is any reason to search issues in server code, because postman works with it perfect. In case, there is no CORS errors: server provides client app. I get nice response from the server, with no cookies. Postman gets them.
axios request in react app:
export const login = createAsyncThunk(
'auth/login',
async (credentials: ILogin) => {
// todo: making a request to server
const response = await axios({
url: '/api' + '/auth' + '/login',
method: 'POST',
data: credentials,
withCredentials: true,
headers: {
'Content-Type': 'application/json'
},
});
console.log(response)
}
)
Client doesn't receive cookies, neither on localhost nor deployed app.
As you see, only place where cookies are shown it's network section in devtools, but everything else, including server acts like my second request hadn't any cookie, because in this case, server would answer like: agh, already logged in
P.S: i'm using http

Cross-site cookie not set in split-stack Rails 7/React 18 app

I have a Rails 7 API and React 18 front end, both deployed to Heroku on separate subdomains. Locally, the Rails app runs on localhost:3000 and the React app on localhost:3001. I need to set a CSRF cookie on the front end from the back end. I'm using the rack-cors gem on the Rails side to handle cross-origin requests. The CSRF cookie is set on the server side when the server gets a request from the front end to create an authenticated session. When the server verifies the authentication token (received by the front end from Google, it is supposed to set a CSRF-TOKEN cookie that will then be set in the browser and included by the front end in subsequent authenticated requests. I can see that the Set-Cookie header is being sent with the following value, but the cookie is not being set in Chrome in my dev environment (the site won't load in Firefox or Safari for other reasons, so I'm unable to test cookie settings in other browsers right now):
Set-Cookie: CSRF-TOKEN=c2Cn8OMs4IhgI5A4g1GC1XjG5hEc6RRW7dSPynxNbgsb0vsoWCr07yulWVzUwFYNP7dD8ARMps3pz5MMngKdog; path=/; secure; SameSite=None
My CORS initializer (/config/initializers/cors.rb) - configatron.client_origin is set to http://localhost:3001 in dev environments or https://sim.danascheider.com in prod):
Rails.application.config.middleware.insert_before 0, Rack::Cors do
allow do
origins configatron.client_origin
resource '*',
headers: :any,
methods: %i[get post put patch delete options head],
credentials: true
end
end
Rails.application.config.action_controller.forgery_protection_origin_check = false
The code that sets the cookie (this works based on inspecting the response in the dev tools):
cookies['CSRF-TOKEN'] = {
value: form_authenticity_token,
domain: :all,
same_site: :none,
secure: true
}
And the code that makes the request from the front end:
export const logInUser = token => {
const uri = `${backendBaseUri}/sessions`
const body = JSON.stringify({ token })
return(
fetch(uri, { method: 'POST', crossDomain: true, body, headers: { 'Content-Type': 'application/json' } })
.then(resp => {
if (resp.status === 401) throw new AuthorizationError()
return resp
})
)
}
I've looked in various other sources and all say to do what I'm already doing.
The cookie ended up being set when I added the credentials: 'include' option to my fetch request. I don't fully understand why this worked or was required on this request, but it did:
export const logInUser = token => {
const uri = `${backendBaseUri}/sessions`
const body = JSON.stringify({ token })
return(
// Replace crossDomain: true (not a real option for 'fetch') with credentials: 'include'
fetch(uri, { method: 'POST', credentials: 'include', body, headers: { 'Content-Type': 'application/json' } })
.then(resp => {
if (resp.status === 401) throw new AuthorizationError()
return resp
})
)
}

Axios post fails with 403 error code and showing CSRF token validation failed but works fine django api when passing csrftoken in header with Postman

I am tring to send csrf token by using axios post in react js but csrf token validation is failing. I am also done with credentials:true
but it's not working.I am receving csrf token in cookie but not able to send it.
class LoginService extends Component {
loginUser(formData)
{
const config = {
headers: { 'content-type': 'application/json',
'X-CSRFToken': Cookies.get("csrftoken")}
}
return axios.post(`${API_BASE_URL}api/user/v1/account/login_session/`,formData,config);
}
}
If you are using httpsOnly cookies then 'X-CSRFToken': Cookies.get("csrftoken")} will not work.
What is HttpOnly?
According to the Microsoft Developer Network,
HttpOnly is an additional flag included in a Set-Cookie HTTP response
header. Using the HttpOnly flag when generating a cookie helps
mitigate the risk of client side script accessing the protected cookie
(if the browser supports it).
Using withCredentials: true should suffice.
loginUser(formData)
{
const config = {
headers: { 'content-type': 'application/json' },
withCredentials: true
}
return axios.post(`${API_BASE_URL}api/user/v1/account/login_session/`,formData,config);
}
}
withCredentials indicates whether or not cross-site Access-Control
requests should be made using credentials
withCredentials: false,
// default
Of course you also need to ensure the cookie domain is set correctly in order for the cookie to work.
Tip: Check your chrome network inspector, and see whether the desire cookie is sent together with your request
CSRF with HttpOnly
If you are setting csrf cookie with httpOnly you can remove httpOnly.
There's an answer with regards to this here https://security.stackexchange.com/a/175540/135413
When you send the header in Axios to the server, you need to know if your server-side accepts the patterns: CSRF-TOKEN or X-CSRF-TOKEN or XSRF-TOKEN or X-XSRF-TOKEN.
You are using X-CSRFToken, that not combine with any patterns shown above.
More here: https://www.npmjs.com/package/csurf
**This is how I solve my problem :
first set these two as:-
axios.defaults.xsrfCookieName = "csrftoken";
axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
**
loginUser(formData)
{
const config = {
headers: { 'content-type': 'application/json'},
"X-CSRFToken": Cookies.get('csrftoken'),
withCredentials: true
}
return axios.post(`${API_BASE_URL}api/user/v1/account/login_session/`,formData,config);
}
}

Axios not authenticating to API

Ive been trying all day to get data from my Asp.Net Api but with no avail. I login and get an authentication token from the server and store it locally but when I try to perform any action that requires authentication, the server returns a 401 response. Is there something Im doing wrong in my code? When I use a tool like postman, everything works okay but not in my app.
This is my login
try {
response = await API.post(AuthUrl, credentials)
if(response.status >= 200 || response.status <= 299){
let Auth = {
Username: response.data.Username,
Roles: response.data.Roles,
Expires: response.data.Expires,
Token: response.data.Token
};
localStorage.setItem(window.location.host, JSON.stringify(Auth));
}
}
This is my axios encapsulator
export default axios.create({
baseURL: BaseUrl,
responseType: "json",
auth: `Bearer ${localStorage.getItem(window.location.host) == null? "" : JSON.parse(localStorage.getItem(window.location.host)).Token}`
})
and this is how im consuming it
try{
const response = await API.get(getUrl)
setLoading(false);
//........Do something with response
}
This is what is logged at the server
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request starting HTTP/2.0 GET https://localhost:44307/api/classes/getclasses/
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executing endpoint 'SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web)'
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "GetClasses", controller = "Classes", page = "", area = ""}. Executing controller action with signature System.Collections.Generic.IEnumerable`1[SchoolManager.Dtos.Tenancy.ClassDto] GetClasses(System.String, System.String) on controller SchoolManager.Web.Controllers.ClassesController (SchoolManager.Web).
Microsoft.AspNetCore.Cors.Infrastructure.CorsService:Information: CORS policy execution successful.
Microsoft.AspNetCore.Authorization.DefaultAuthorizationService:Information: Authorization failed.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Authorization failed for the request at filter 'Microsoft.AspNetCore.Mvc.Authorization.AuthorizeFilter'.
Microsoft.AspNetCore.Mvc.ChallengeResult:Information: Executing ChallengeResult with authentication schemes (Bearer).
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler:Information: AuthenticationScheme: Bearer was challenged.
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executed action SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web) in 146.8824ms
Microsoft.AspNetCore.Routing.EndpointMiddleware:Information: Executed endpoint 'SchoolManager.Web.Controllers.ClassesController.GetClasses (SchoolManager.Web)'
Microsoft.AspNetCore.Hosting.Internal.WebHost:Information: Request finished in 218.2724ms 401
The way the axios.create method is used is not right.
Ref: https://github.com/axios/axios#request-config
The documentation clearly shows that config auth: indicates that HTTP Basic auth should be used, and supplies credentials. For Bearer tokens and such, use Authorization custom headers instead so in your case you can do something like this
export default axios.create({
baseURL: BaseUrl,
responseType: "json",
headers: {'Authorization': "bearer " + JSON.parse(localStorage.getItem(window.location.host)).Token}})

How can we send OAuth2.0 with axios in React js

I am working on one authentication problem where i have to implement OAuth2.0 authentication for my React App. Is there any way that i can use that authentication with Axios Promise based library???
You will have to pass your Token in the header.
See below;
const instance = axios.create({
baseURL: 'http://localhost/api/',
headers: {'Authorization': 'basic '+ token}
});
instance.get('/path')
.then(response => {
return response.data;
})
OR
Set an Authorization cookie in the browser once you get your token.
The browser will always send the Authorization cookie in each request made to the server. You won't have to pass it through Axios get/post.
UPDATE:
In order to get the access token from the oAuth server, pass the client_id, client_secret, scope and grant_type as follows;
var axios = require("axios");
axios.request({
url: "/oauth/token",
method: "post",
baseURL: "http://sample.oauth.server.com/",
auth: {
username: "myUsername", // This is the client_id
password: "myPassword" // This is the client_secret
},
data: {
"grant_type": "client_credentials",
"scope": "public"
}
}).then(respose => {
console.log(respose);
});
I am assuming that you are using grant_type = "client_credentials", if not, then based on the grant_type you use, you will have to also change the request parameters accordingly.

Resources