Laravel Sanctum with Redux Toolkit & Axios - reactjs

I have a backend with laravel sanctum implementation, all config are working correctly.
On the frontend I use react, an action is dispatched to login, for example.
X-XSRF-TOKEN must be set before hitting the endpoint, thus I must call /sanctum/csrf-cookie first, I've ran into these problems:
If I try and get the csrf token in createAsyncThunk first, and then /login, redux toolkit assumes the request is fulfilled, thus changing the state before the login api is called
I have no idea why when I create an axios instance, the X-XSRF-TOKEN is not being set in the header, but when I use axios imported normally, it works.
Action
export const login = createAsyncThunk("auth/login", async (_) => {
try {
request.get("sanctum/csrf-cookie").then((response) => {
// THE PROBLEM OCCURS HERE, no header is received, (request is instantiated below, if I import axios it works)
const res = await request.post("/login", _);
return res.data;
// this one failed with 419 csrf token mismatch, eventually because X-XSRF-TOKEN is not set, im not sure why
});
} catch (err) {
throw err;
}
});
axios
import axios from "axios";
export const request = axios.create({
baseURL: `http://localhost:8000/`,
headers: {
Accept: "application/json",
},
});
axios.defaults.withCredentials = true;
Note: The backend is working and configued correctly, because if I do not create an instance and just import and use axios, the request is working, but I'm back with problem 1, where the thunk is fulfilled on the first request (before return response.data), and i'm not interested in such a solution, I don't need to copy the code
Summary:
The sanctum/csrf-cookie response has no X-XSRF-TOKEN header, only when creating an axios instance using axios.create, a cookie is received though
Perhaps the header is resetting to its default on the second request? how can I append the headers from the first request? Thanks
-- Should I try instead of calling sanctum/csrf-cookie in all requests like this, intercept the request and somehow append the header to the request?
Any help, examples, guides are appreciated

rookie mistake, was using axios.defaults.withCredentials = true;
instead of axiosInstance.defaults.withCredentials = true;

Related

Should I store a Axios instance configured with interceptors that use a state value in a state or ref or useMemo?

Similiar to Should we use axios inside both components and store?
I have a state called authorization which contains the Bearer token value that would be used in Axios calls. The state is available in a context and accessible using the useContext hook.
I create the AxiosInstance where I add a interceptors.request.use to add the Authorization header.
What I've done so far was useMemo with the authorization value a a dependency. But since Axios operation is asynchronous it seems that I may get the wrong axios instance.
I did a bit of refactoring using useRef and I still had a bit of an issue.
What I then did was implement the Observer pattern and send a notification to the component that provides the Axios client that the authorization header was changed and update the ref. However, again there's still some cases where the old client is being invoked.
What I am wondering is should I store it in useState or is there a fundamental problem with the approach of storing the Axios client and instead should I just bite the bullet and create a new axios client per request which takes the authorization header that's presently in the state?
The way I typically do it is to save the authentication information in a React context or to redux, and create axios instances as needed to access the authorization token.
Maybe something like:
const getBearerToken() => { ... [your implementation to retrieve from context here] ... };
const webserviceRequest = (url) => () => axios.create({
baseURL: url,
... [AxiosRequestConfig options here] ...
headers: {
Authorization: `Bearer ${getBearerToken()}`,
...
},
});
Then, you can define your webservice requests by invoking this function, e.g.:
const sampleGetRequest = () => webserviceRequest(SAMPLE_URL)().get('');
const samplePostRequest = (postData) => webserviceRequest(SAMPLE_URL)().post('', postData);
These return a Promise<AxiosResponse> which you can call as normal, e.g.
sampleGetRequest().then((response) => { ... }).catch((error) => { ... })
and so on.
The key point is that the webserviceRequest returns a function which creates an asynchronous webservice request with the current authorization token. You don't ever save the webserviceRequest function because that authorization token can become stale.
With the dependency on React context, I'd avoid using interceptors for this (I'd avoid using Axios all together but that's just my opinion).
Instead, try creating a custom hook
import axios from "axios";
import { useContext } from "react";
const api = axios.create({ /* baseURL, etc */ });
const useApi = () => {
const authorization = useContext(AuthContext); // guessing
if (authorization) {
api.defaults.headers.common.Authorization = `Bearer ${authorization}`;
} else {
delete api.defaults.headers.common.Authorization;
}
return api;
};

How can I access redux store outside a component?

I've a API file and a token in redux store. I've tried with getState() but the token will stored after a user signs in so it's giving me error of undefined. How do I solve this problem?
const token = store.getState().verification.token;
import axios from "axios";
const ApiInstance = axios.create({
});
// console.log(process.env);
ApiInstance.defaults.headers.post["content-type"] = "application/json";
ApiInstance.defaults.headers.get["Accept"] = "application/json";
ApiInstance.defaults.headers.get["Authorization"] =
`Bearer ${token}`;
export default ApiInstance;
Use Axios Interceptor! :)
You shouldn't use .defaults for authorization in most cases anyway because ideally your token will expire after some time period. A better approach would be to use an axios interceptor.
Bascially axios lets you define a function that will run EVERY time an HTTP request is made. In that function you can call store.getState() and then append the token to the request headers.
For example, you could use the following:
ApiInstance.interceptors.request.use(req => {
let myToken = store.getState().verification.token;
req.headers.authorization = `Bearer ${myToken}`;
return req;
});

Axios - New Instance overrides Global Defaults

quick question on implementation of multiple axios instances.
I call my own API as needed inside my mobile app. Certain API endpoints are protected by checking that the token issued by Google Signin or Facebook Signin is authenticated and matches the expected user. Other endpoints do not require a token and actually require the frontend to not send a token at all to proceed down the proper logic path on the backend.
I set the global axios instance when I fetch the user token on frontend. I am hoping that all axios calls include the token by default. To achieve this, I set the default Authorization header as such:
import axios from 'axios'
...
axios.defaults.headers.common.Authorization = `Bearer ${fbUserInfo.accessToken}`
This code runs on the initial page of the app that is loaded, so everytime I use axios somewhere else in the app, the token is now included. It works as expected until I proceed to the following step.
In order to create some recurring API calls where a token should not be included, I create a "tokenless" instance inside one of my redux action creators.
const axiosTokenlessInstance = axios.create()
...
axiosTokenlessInstance.defaults.headers.common.Authorization = false
This does indeed override the default axios settings when I call it directly, however, I've realized that it also has overridden my default settings when I just call axios directly again.
I expected the behavior to be that I could still call axios.post or axios.get elsewhere and it would still include the token. Was I mistaken and do I have to create a "tokened" instance? I'd prefer not to do this as I'd have to go through and replaced tokened instance explicitly everywhere rather than just using axios default. Thanks!
Try creating a file called setAuthToken.js that handles the auth instance separately. This way any time you need to use the auth, you can just call this function ( I would even suggest limiting to calling this auth only once and having the token saved in the users localStorage):
import axios from 'axios';
const setAuthToken = token => {
if(token){
axios.defaults.headers.common['x-auth-token'] = token;
}
else{
delete axios.defaults.headers.common['x-auth-token'];
}
}
export default setAuthToken;
Now let's say you have a backend function call that needs auth verification, you can do it like so:
import setAuthToken from "../utils/setAuthToken";
export const loginUser = (token) => async (dispatch) => {
if (localStorage.token) {
setAuthToken(localStorage.token);
}
try {
const res = await axios.get("/api/auth");
dispatch({
type: USER_LOGGED,
payload: res.data,
});
} catch (err) {
dispatch({
type: AUTH_ERROR,
});
}
};
Specifying the blank header Authorization inside axios.create() fixes the issue and doesn't override the global axios headers.
So this works as expected:
const axiosTokenlessInstance = axios.create({
baseURL: Config.API_HOST,
headers: { Authorization: '' },
})
While this overrides global axios settings, not just affecting the specified instance:
const axiosTokenlessInstance = axios.create()
axiosTokenlessInstance.defaults.headers.common.Authorization = false

axios.post gives CORS issue whereas GET is working fine in React using Laravel API

I am trying to create a post using LARAVEL 5.7 local REST api using a React app. I am using axios.post method with crossOrigin:true . I do not understand why get is working and post is not working. Laravel api is on wamp based localhost and React app is running at localhost:3000 My actions/index.js is as follows and the relevant action creator is addPost():-
import axios from 'axios';
export const FETCH = 'FETCH_POSTS';
export const ADD = 'ADD_POST';
const URL = 'http://postsapi.test/api/v1';
export function fetchPosts(){
const url = `${URL}/posts`;
const request = axios.get(url, { crossDomain: true });
//console.log(request);
return{
type: FETCH,
payload: request
}
}
export function addPost(post){
const url = `${URL}/posts`;
console.log(url);
const request = axios.post(url,post);
//console.log(request);
return{
type: ADD,
payload: request
}
}
This is the snap shot from browser that shows in preflighted request Access-Control-Allo-Origin is set twice as well
And in console I am getting this issue
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://postsapi.test/api/v1/posts. (Reason:
Multiple CORS header ‘Access-Control-Allow-Origin’ not allowed).
This happens because the server and client is on different origins.
To overcome this you have to enable Cross-Origin Resource Sharing in your Laravel server as a middleware.
This thread might help you.

React + axios + redux interceptors

I have an React + redux + axios app, white jwt authentication. So, i need to intercept every request to server and set header with token. The question is where I have to set this headers, or implement interceptors. (also, i need redux store in scope, to get tokens from store). My idea - implement it in the Index component. Is it right way?
I suggest you to set the header axios.defaults.headers.common.authorization. Take a look here Global axios defaults. If you need a working example, this public repo can help you out.
Why do you have to manually set the header. Can't you just store the JWT in a cookie and then the browser will forward it automatically for you. Just make sure you pass credentials: 'include' in your HTTP options.
create a redux-middleware to do these things.
Apart from acting like interceptor to add header token,
you also do request/response transformation.
Also,you can mention the next action to which you want to dispatch the result if you don't want to return the promise and result.
Middleware gives you a chance to get the store state and also fetch & dispatch other action
const apiInterceptor = store => next => action => {
if(action.type !== 'ajax') return next(action)
const state = store.getState();
state.token // get token here
//add this api check part in some other module.
if(action.api=='UPDATE_CLIENT')
{
method = 'post';
url = 'some url';
}
else
{
method = 'get';
url = 'other url';
}
fetch(url, {
method: method,
headers : 'add token here',
body: JSON.stringify(action.body())
})
.then(response => response.json())
.then(json => json)//either return result
//OR dispatch the result
.then(json => {
dispatch({type:action.next_action,payload : json})
})
}
Integrate the middleware with store.
import customMiddleware from './customMiddleware'
const store = createStore(
reducer,
applyMiddleware(customMiddleware)
)
I offer you to refer redux-thunk.
You must create api-wrapper-helper to inject to redux-thunk as extra argument, then access to api-wrapper-helper from redux actions.
api-wrapper-helper is a function that get url and method as argument and send request to api-server then return result. (you can set headers in this file)
For example you can see ApiClient.js of react-redux-universal-hot-example boilerplate.
This is an old post but its getting a few views, so something like this would work nicely and is easily testable.
apiclient.js
import axios from 'axios';
import { setRequestHeadersInterceptor } from './interceptors';
const apiClient = axios.create(
{
baseUrl: 'https://my.api.com',
timeout: 3000
});
apiClient.interceptors.request.use(setRequestHeadersInterceptor);
export default apiClient;
interceptors.js
export const setRequestHeadersInterceptor = config =>
{
// have each interceptor do one thing - Single Responsibility Principle
};
you should store your auth details in a httpOnly secure cookie, the transmission to/from the server will be automatic then
// Interceptor
axios.interceptors.response.use(function (response) {
// success case here
return response
}, function (error) {
// Global Error Handling here
// showErrorToaster(error['message']);
return Promise.reject(error.response.data)
})

Resources