Axios - New Instance overrides Global Defaults - reactjs

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

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

NextAuth: How can I attach my JWT token to every axios call?

I am building a Next.js application.
For authentication, I am using NextAuth.
For making HTTP calls, I am using Axios.
The problem I am facing is that I want to attach the JWT with every axios call I make.
I have an axios instance created as:
axios-client.js
const ApiClient = () => {
const defaultOptions = {
baseURL,
};
const instance = axios.create(defaultOptions);
instance.interceptors.response.use(
(response) => {
return response;
},
(error) => {
console.log(`error`, error);
throw new Error(error.response.data.message);
},
);
return instance;
};
export default ApiClient();
I can get the jwt from getSession() function provided by next-auth.
But the problem is that function is asynchronous. If I try to get jwt from this from the getSession() function, I always get a "Promise" instead of value.
PS: I am using Strapi which sends the JWT after successful login.
Why can't you await the response from getSession() and add it as a header to your request. Note that you don't have to reinitialize axios client each time, you can reuse it.

How to make functions exposed by useAuth hook available in plain JS files/modules

The auth logic is implemented and encapsulated in the AuthProvider and can be accessed using the useAuth() hook. We can invoke the useAuth() from either another hook or a functional component. But I need to access its two functions, namely getAccessToken and refreshAccessToken from a JS module named axios.ts that has nothing to with React. How can this be done?
// AuthProvider.ts
export const AuthProvider: React.FC = ({ children }) => {
//useState to set the local state in this component.
login();
logout();
getAccessToken(); <------ get the access token from the local storage
refreshAccessToken();
}
export const useAuth = () => useContext(AuthContext);
Note that refreshAccessToken() is setting the local state in the AuthProvider component and making a call to an api endpoint to refresh the token.
Based on the answer given in this post, I created a separated module to register axios interceptors (instead of registering them in a React component) so that they are loaded only once.
axios-auth-refresh is a stable third-party module to intercept the requests and add authorization header to them and also makes calls to the refreshToken endpoint.
// axios.ts
import createAuthRefreshInterceptor from "axios-auth-refresh";
const axiosApiInstance = axios.create({
baseURL: apiEndipoint,
});
axiosApiInstance.interceptors.request.use((request) => {
// Inject the token in request header
if (getAccessToken()) { <---- cannot access getAccessToken because React does not allow to
use the hooks in plain JS files
request.headers.Authorization = `Bearer ${getAccessToken()}`,
}
return request;
});
createAuthRefreshInterceptor(
axiosApiInstance,
refreshAccessToken, <---- for the same reason, cannot obtain a reference
to the refreshAccessToken defined in AuthProvider
axiosAuthRefreshOptions
);
I need to access the getToken() and refreshAccessToken() in axios.ts to make it work. getToken() simply returns the token from the local storage and thus, as a workaround, I can access the local storage directly in the axios.ts.
But how to access refreshAccessToken, pl. guide.

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;

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