T
TanStack2y ago
like-gold

refreshing access token manually in axios

In my axios API calls, I need to give an Authorization header with the access token -
const res = await axios.get(
`https://us-east-1.aws.data.mongodb-api.com/app/${process.env.REACT_APP_APP_ID}/endpoint/taskLogs`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
params: {
taskId,
},
}
);
const res = await axios.get(
`https://us-east-1.aws.data.mongodb-api.com/app/${process.env.REACT_APP_APP_ID}/endpoint/taskLogs`,
{
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: "application/json",
},
params: {
taskId,
},
}
);
Question - How should I ideally be handling this refreshing of the access token? What I am doing right now - Using a useAccessToken hook as follows -
import { useRealmApp } from "src/store/RealmApp";
import { decodeJwt } from "jose";

export const useAccessToken = () => {
const app = useRealmApp();
const getValidAccessToken = async (): Promise<string | null> => {
const accessToken = app.currentUser?.accessToken;
const payload = decodeJwt(accessToken);
if (payload.exp) {
const isExpired = payload.exp * 1000 < Date.now();
if (isExpired) {
// refresh the custom data which will also refresh the access token
await app.currentUser?.refreshCustomData();
return app.currentUser?.accessToken;
} else {
return accessToken;
}
} else {
console.log("Failed to decode the access token");
}
return null;
};

return getValidAccessToken;
};
import { useRealmApp } from "src/store/RealmApp";
import { decodeJwt } from "jose";

export const useAccessToken = () => {
const app = useRealmApp();
const getValidAccessToken = async (): Promise<string | null> => {
const accessToken = app.currentUser?.accessToken;
const payload = decodeJwt(accessToken);
if (payload.exp) {
const isExpired = payload.exp * 1000 < Date.now();
if (isExpired) {
// refresh the custom data which will also refresh the access token
await app.currentUser?.refreshCustomData();
return app.currentUser?.accessToken;
} else {
return accessToken;
}
} else {
console.log("Failed to decode the access token");
}
return null;
};

return getValidAccessToken;
};
Not at all sure if this is what the recommended way is (for example - using a library like jose or an equivalent to decode the jwt). Would really appreciate some confirmation before I make changes in all my query hooks!
5 Replies
afraid-scarlet
afraid-scarlet2y ago
I use something like this. Note we usually try and refresh the token on any 401 error.
const config: CreateAxiosDefaults = {
//config here
}

const AxiosInstance = axios.create(config);

AxiosInstance.interceptors.request.use(
(v) => {
const token = window.localStorage.getItem("token");

if (token) v.headers.Authorization = `Bearer ${token}`;

return v;
},
e => Promise.reject(e),
);

AxiosInstance.interceptors.response.use(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
r => r,
async (e: AxiosError) => {
const request = e.config;

if (e.response?.status !== 401 || !request) return Promise.reject(e);

try {
const refreshToken = localStorage.getItem("refreshToken");
const response = await AxiosInstance.post("/api/whatever", { refreshToken });

const token = response.data as string;

localStorage.setItem("token", token);

request.headers.Authorization = `Bearer ${token}`;
return axios(request);

} catch (e) {
// do whatever
}
},
);
const config: CreateAxiosDefaults = {
//config here
}

const AxiosInstance = axios.create(config);

AxiosInstance.interceptors.request.use(
(v) => {
const token = window.localStorage.getItem("token");

if (token) v.headers.Authorization = `Bearer ${token}`;

return v;
},
e => Promise.reject(e),
);

AxiosInstance.interceptors.response.use(
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
r => r,
async (e: AxiosError) => {
const request = e.config;

if (e.response?.status !== 401 || !request) return Promise.reject(e);

try {
const refreshToken = localStorage.getItem("refreshToken");
const response = await AxiosInstance.post("/api/whatever", { refreshToken });

const token = response.data as string;

localStorage.setItem("token", token);

request.headers.Authorization = `Bearer ${token}`;
return axios(request);

} catch (e) {
// do whatever
}
},
);
You can change it how you like. I recommend looking at some examples. This is what I could come up with quickly (don't have a lot of time now). However you could do something like this
genetic-orange
genetic-orange2y ago
Most solutions I've seen on internet look like that but the main problem is that if you have many parallel requests ending up in 401, you'll have many refreshToken requests which could cause issues (new token invalidate old one so the first request to get a new token ends up in a 401 again and you have an infinite loop) I've seen solutions where requests to be retried are put in a queue and this queue is consumed when a refresh token is obtained the first time but even that seems to bug sometimes in my use case
like-gold
like-goldOP2y ago
why not refresh the token based on whether it is expired or not? You have that info with you in the JWT before you make the request..
fascinating-indigo
fascinating-indigo2y ago
I have been struggliing with this exact same issue. So this thread definitely helps. The access token I am getting from Microsoft expires every hour. So after an hour, my requests to my backend start failing. I was going to throw the refetch logic in my axios interceptor, but the issue with that is I need to call a hook to get the current logged in instance: const { instance } = useMsal();. So since my axios interceptors are not in a functional component, that does not work.
optimistic-gold
optimistic-gold2y ago
I feel like this is a common question I see with lots of possible answers. We use zustand to store the user auth which is accessible inside our interceptors (we use ky not axios, but the idea is the same). We have client side timeouts that open prompts when the session is expiring in case they want to stay logged in, so on a 200 request we useAuthStore.getState().refresh(). Calling that triggers a component that uses useAuthStore() to restart our timeouts. Definitely not answering any specific question on this thread, sorry, just throwing some ideas out there

Did you find this page helpful?