How can I access redux store outside a component? - reactjs

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;
});

Related

axios interceptor not resending failed request

I am creating this React dashboard app. I'm currently trying to set up an axios interceptor to refresh my access token when I send a request with an expired access token to my backend.
I have a refresh async action in my slice that makes a get request to my backend with my refresh token cookie. That is being called from my axiosInstance response interceptor.
After that returns, I set the access token in my store
Then try to rerun the original failed request
return axiosInstance(originalRequest)
The problem is the return statement to rerun the request is not working. when I view my devtools network tab I only see the preflight, failed post request, then refresh request.
There should be another retry of the first post request after the refresh
Here is my axios code
-Creating my instance and interceptors
import axios from 'axios';
import { store } from '../../../app/store';
import { refresh } from '../../auth/authSlice';
const axiosInstance = axios.create({
timeout:5000
})
axiosInstance.defaults.baseURL = process.env.REACT_APP_BASE_SCRAPE_URL
axiosInstance.defaults.withCredentials = true;
axiosInstance.interceptors.request.use(function(config){
const state = store.getState();
const token = state.auth.accessToken
config.headers.Authorization = `Bearer ${token}`
return config
})
axiosInstance.interceptors.response.use(resp => resp,async error =>{
const orginalRequest = error.config
console.log(error.response);
if(error.response.data === "Invalid Access Token"){
store.dispatch(refresh({}))
return axiosInstance(orginalRequest)
}
})
export default axiosInstance
Using the axios Instance (Request that needs to rerun after initial failed request)
import axiosInstance from "./protectedScrapeAxios"
export const start = async (pageName, postCount) =>{
const resp = await axiosInstance.post('/start', {pageName: pageName, postCount: postCount})
return resp.data
}

React Axios instance : How to get Token from redux store to put it on my axios instance

I want to create an instance of Axios to use for my HTTP calls, the problem is the token stored on my redux, I don't know how to get it and put it in my configuration, because UseSelector is a part of the functional component but in my case, I have a javascript configuration like this :
import axios from 'axios';
const axiosClient = axios.create();
axiosClient.defaults.baseURL = 'https://example.com/api/v1';
axiosClient.defaults.headers = {
'Content-Type': 'application/json',
Accept: 'application/json'
};
//All request will wait 2 seconds before timeout
axiosClient.defaults.timeout = 2000;
axiosClient.defaults.withCredentials = true;
Does anyone of you know how to get the Token variable from redux in this case, please?
thank you!
There are multiple ways to do it, and I like the Axios interceptor approach.
const instance = axios.create({
baseURL: "base_url",
});
instance.interceptors.request.use(function (config) {
const token = store.getState()?.user?.userData?.token;
config.headers.Authorization = token;
return config;
});

Laravel Sanctum with Redux Toolkit & Axios

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;

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

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