How to refresh token in axios? - javascript

My question is related to customAxios.interceptors.response.use . My purpose here is; if the token expired and I got a 401 error, make a request again where I got a 401 error and write the new token to the headers. On the other hand, if I get an error except for the 401 error, show me the error.response.data . Do you think this logic is set up correctly? I tried to test but I wasn't sure especially 401 error cases
import axios from "axios";
import { LoginAPI } from "../playwright/tests/login/login.api";
import { test } from "#playwright/test"
import {configEnv} from "../config/config"
test.beforeAll(async () => {
await LoginAPI.API.Signin.run()
});
const customAxios = axios.create({
baseURL: configEnv.apiBaseURL
});
customAxios.interceptors.request.use(
async (config) => {
if (config.headers) {
config.headers['Authorization'] = `Bearer ${LoginAPI.States.token}`;
return config;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
customAxios.interceptors.response.use(
function(response) {
return response;
},
async function(error) {
if (401 === error.response.status) {
await LoginAPI.API.Signin.run()
customAxios.defaults.headers.common['Authorization'] = `Bearer ${LoginAPI.States.token}`
} else {
return Promise.reject(error.response.data);
}
}
);
export default customAxios

I would recommend you to store your token in a localStorage and then replace it after refresh. This way you can set a token in your API class in one place.
import axios from "axios";
export const ApiClient = () => {
// Create a new axios instance
const api = axios.create({
baseURL: "URL",
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
// Add a request interceptor to add the JWT token to the authorization header
api.interceptors.request.use(
(config) => {
const token = sessionStorage.getItem("jwtToken");
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
// Add a response interceptor to refresh the JWT token if it's expired
api.interceptors.response.use(
(response) => response,
(error) => {
const originalRequest = error.config;
// If the error is a 401 and we have a refresh token, refresh the JWT token
if (
error.response.status === 401 &&
sessionStorage.getItem("refreshToken")
) {
const refreshToken = sessionStorage.getItem("refreshToken");
let data = JSON.stringify({
refresh_token: refreshToken,
});
post("/refreshToken", data)
.then((response) => {
sessionStorage.setItem("jwtToken", response.token);
sessionStorage.setItem("refreshToken", response.refresh_token);
// Re-run the original request that was intercepted
originalRequest.headers.Authorization = `Bearer ${response.token}`;
api(originalRequest)
.then((response) => {
return response.data;
})
.catch((error) => {
console.log(error);
});
// return api(originalRequest)
})
.catch((err) => {
// If there is an error refreshing the token, log out the user
console.log(err);
});
}
// Return the original error if we can't handle it
return Promise.reject(error);
}
);
const login = (email, password) => {
return api
.post("/authentication_token", { email, password })
.then(({ data }) => {
// Store the JWT and refresh tokens in session storage
sessionStorage.setItem("jwtToken", data.token);
sessionStorage.setItem("refreshToken", data.refresh_token);
})
.catch((err) => {
// Return the error if the request fails
return err;
});
};
const get = (path) => {
return api.get(path).then((response) => response.data);
};
const post = (path, data) => {
return api.post(path, data).then((response) => response.data);
};
const put = (path, data) => {
return api.put(path, data).then((response) => response.data);
};
const del = (path) => {
return api.delete(path).then((response) => response);
};
return {
login,
get,
post,
put,
del,
};
};
Best,
Chris

Related

Component doesn't re-render on follow up request after token refresh

This is my axios-hoook.js and I am using axios-hooks package.
import useAxios from 'axios-hooks';
import axios from 'axios';
import LocalStorageService from './services/local-storage.service';
import refreshToken from './refresh-token';
axios.defaults.baseURL = 'http://localhost:3000/api/v1';
axios.defaults.transformResponse = [
(responseData) => {
const { data, error } = JSON.parse(responseData);
return error || data;
},
];
// request interceptor to add token to request headers
axios.interceptors.request.use(
async (config) => {
const token = LocalStorageService.getAccessToken();
if (token) {
config.headers = {
authorization: token,
};
}
return config;
},
(error) => Promise.reject(error)
);
// response interceptor intercepting 401 responses, refreshing token and retrying the request
axios.interceptors.response.use(
(response) => response,
(error) => {
const { config } = error;
if (error.response?.status === 401 && !config._retry) {
config._retry = true;
refreshToken(LocalStorageService.getRefreshToken())
.then((res) => {
const { accessToken } = res.data.data;
LocalStorageService.setAccessToken(accessToken);
return axios(config);
})
.catch((err) => {
if (err.response.status === 401) {
LocalStorageService.setUser(null);
window.location.href = '/login';
}
return Promise.reject(err);
});
}
return Promise.reject(error);
}
);
export default useAxios;
This is the Course.jsx where it is being used.
const Course = () => {
const [{ data: courses = [] }, refetchCourse] = axiosHook(ApiConfig.COURSE.GET_COURSES.url);
return (
<Datatable
entity={entity}
columns={courseColumns}
rows={courses}
deleteRow={handleDeactivate}
viewRow={handleView}
/>
)
}
Image
In the axios-hooks docs there's a link to a working example to implement a refresh token feature. It is running in CodeSandbox at this link https://codesandbox.io/s/axios-hooks-authentication-zyeyh.
Compare that with your example and you'll find the reason why yours doesn't work.
There was an error in my code. I was not returning the promise.
axios.interceptors.response.use(
(response) => response,
(error) => {
const { config } = error;
if (error.response?.status === 401 && !config._retry) {
config._retry = true;
return refreshToken(LocalStorageService.getRefreshToken()) // this line
.then((res) => {
const { accessToken } = res.data.data;
LocalStorageService.setAccessToken(accessToken);
return axios(config);
})
.catch((err) => {
if (err.response.status === 401) {
LocalStorageService.setUser(null);
window.location.href = '/login';
}
return Promise.reject(err);
});
}
return Promise.reject(error);
}
);

How to check email overlap using Axios get and post for SignUp with React?

I'm trying to filter overlap validation for SignUp email.
in my api.js
const token = JSON.parse(localStorage.getItem('token'));
const api = axios.create({
baseURL: baseURL, // already set our base URL
headers: {
Authorization: `Bearer ${token}`,
'Access-Control-Allow-Origin': '*',
}
});
and in my authService.js
const register = (countryCode, name, email, password) => {
return axios
.post('/auth/signup', {
countryCode,
name,
email,
password,
})
.then((response) => {
if (response.headers.authorization) {
console.log(response);
localStorage.setItem('user', JSON.stringify(response.headers.authorization));
}
return response.headers.authorization;
});
};
const login = (email, password) => {
api
.post('/auth/signin', {
email,
password,
})
.then((response) => {
if (response.headers.authorization) {
localStorage.setItem('user', JSON.stringify(response.headers.authorization));
}
return response.data;
});
};
const checkEmail = (email) => {
return api.get('/public/email', { email }).then((response) => {
if (response.data.exist === true) {
return localStorage.getItem('user', JSON.stringify(response.data));
}
return response.data;
});
};
This checkEmail will be in the SignUp.js
for onChange={emailChange}
const onChangeEmail = (e) => {
const email = e.target.value;
if (!email.includes('#')) {
setEmailError('Invalid email');
} else if (email.includes('#')) {
setEmailError(null);
}
AuthService.checkEmail(email).then(
(response) => setEmailError('Already Registered Email'),
(error) => {
console.log(error);
}
);
setEmail(email);
};
after this code,
in the console
it error
Error: Request failed with status code 401
at createError (createError.js:16)
at settle (settle.js:17)
at XMLHttpRequest.handleLoad (xhr.js:62)
I think inn the api.get(URl, {something})
that {something} is wrong but I don't have any idea for hours...
what can I do for this error??
you can't send body parameter in GET , for that POST,PUT will work,
to send with GET then attach data to the GET url.
example
if your using node server at backend then
api.get('/public/email/'+email).then((resp)=>{
log(resp);
}
collect the data using
router.get("public/email/:youremail",(req,res)=>{
req.param.youremail
}

How to add refresh token function in react js redux

I am working on an app that has refresh token functionality. For that, I tried to implement this function after learning about Axios interceptor online. But still, it is not resolved. this how I added this.
I don't know whether it is right or wrong. I just tried implementing refresh token. I had no idea of refresh token before.
Any help would be great.
index.js
axios.interceptors.request.use(
(config) => {
console.log("step-1", config);
const token = localStorageService.getAccessToken();
if (token) {
config.headers["Authorization"] = "Bearer" + token;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
axios.interceptors.response.use(
(response) => {
console.log("step-2", response);
return response;
},
function (error) {
const originalRequest = error.config;
// if (error.response && error.response.status === 401 && !originalRequest._retry) {
// history.push("/");
// return Promise.reject(error);
// }
if (
error.response &&
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
const token = UserServices.getOAuth2().createToken(
"refresh_token",
localStorageService.getRefreshToken(),
{ grant_type: "refresh_token" }
);
return token
.refresh()
.then((res) => {
console.log("step3", res);
if (res.status === 201) {
// 1) put token to LocalStorage
localStorageService.setToken(res.data);
// 2) Change Authorization header
axios.defaults.headers.common["Authorization"] =
"Bearer " + localStorageService.getAccessToken();
// 3) return originalRequest object with Axios.
return axios(originalRequest);
}
})
.catch((error) => {
// Dispatch Logout Function here
store.dispatch({
type: LOGIN_ERROR,
});
localStorageService.clearToken();
});
}
}
);
userServices.js
const localStorageService = LocalStorageService.getService();
class UserServices {
getOAuth2 = () => {
var ClientOAuth2 = require("client-oauth2");
const OAuth2 = new ClientOAuth2({
clientId: "development",
clientSecret: "development",
accessTokenUri: "https://api.xxxx.in/oauth/token",
authorizationUri: "https://api.xxxx.in/oauth/authorize",
redirectUri: "https://api.xxxx.in/oauth/callback",
scopes: ["read", "write", "trust"],
});
return OAuth2;
};
logout() {
localStorageService.clearToken();
}
}
I believe you need to call resolve in you error handler and it should fix it:
return token
.refresh()
.then((res) => {
console.log("step3", res);
if (res.status === 201) {
// 1) put token to LocalStorage
localStorageService.setToken(res.data);
// 2) Change Authorization header
axios.defaults.headers.common["Authorization"] =
"Bearer " + localStorageService.getAccessToken();
// 3) return originalRequest object with Axios.
res(axios(originalRequest)); // <- call resolve here
}
})
.catch((error) => {
// Dispatch Logout Function here
store.dispatch({
type: LOGIN_ERROR,
});
localStorageService.clearToken();
});

Axios multiple request on interceptor

I'm using the library axios in my react app.
I'm having a problem with the interceptor.
My question is let say I have three requests happening concurrently and I don't have the token, the interceptor calling the getUserRandomToken three time, I want the interceptor will wait until I'm getting the token from the first request and then continue to the others.
P.S. the token he is with an expiration date so I also checking for it and if the expiration date is not valid I need to create a new token.
This is the interceptor:
axios.interceptors.request.use(
config => {
/*I'm getting the token from the local storage
If there is any add it to the header for each request*/
if (tokenExist()) {
config.headers.common["token"] = "...";
return config;
}
/*If there is no token i need to generate it
every time create a random token, this is a axios get request*/
getUserRandomToken()
.then(res => {
/*add the token to the header*/
config.headers.common["token"] = res;
return config;
})
.catch(err => {
console.log(err);
});
},
function(error) {
// Do something with request error
return Promise.reject(error);
}
);
How about singleton object that will handle the token generations? something similar to this:
const tokenGenerator ={
getTokenPromise: null,
token: null,
getToken(){
if (!this.getTokenPromise){
this.getTokenPromise = new Promise(resolve=>{
/*supposed to be a http request*/
if (!this.token){
setTimeout(()=>{
this.token = 'generated';
resolve(this.token);
},0)
}else{
resolve(this.token);
}
})
}
return this.getTokenPromise;
}
you can reference this same object from the interceptors.
see example: JS FIddle
reference: reference
You can return a Promise from interceptor callback to "wait" until promise fullfiles (this will fit your case). Check out this example:
function axiosCall () {
return new Promise((resolve, reject) => {
Axios.post(URL, {apiKey}).then((response) => {
resolve(response.data.message);
}).catch((error) => {
reject(error);
});
});
}
instance.interceptors.request.use((config) => {
return axiosCall().then((tokenResponse) => {
setWebCreds(tokenResponse);
config.headers.Authorization = `Bearer ${tokenResponse}`;
return Promise.resolve(config)
}).catch(error => {
// decide what to do if you can't get your token
})
}, (error) => {
return Promise.reject(error);
});
More details here: https://github.com/axios/axios/issues/754
Following code doing certain tasks:
Update Token on 401
Make a queue of failed requests while the token is refreshing.
Restore the original request after token refreshing.
Once the peculiar request is given 200, remove it from the queue.
Config.js
import axios from 'axios';
import { AsyncStorage } from 'react-native';
import { stateFunctions } from '../../src/sharedcomponent/static';
const APIKit = axios.create({
baseURL: '',
timeout: 10000,
withCredentials: true,
});
const requestArray = [];
// Interceptor for Request
export const setClientToken = token => {
APIKit.interceptors.request.use(
async config => {
console.log('Interceptor calling');
let userToken = await AsyncStorage.getItem('userToken');
userToken = JSON.parse(userToken);
config.headers = {
'Authorization': `Bearer ${userToken}`,
'Accept': 'application/json',
"Content-Type": "application/json",
"Cache-Control": "no-cache",
}
// console.log('caling ' , config)
return config;
},
error => {
Promise.reject(error)
});
};
// Interceptor for Response
APIKit.interceptors.response.use(
function (response) {
if (requestArray.length != 0) {
requestArray.forEach(function (x, i) {
if (response.config.url == x.url) {
requestArray.splice(i, 1);
}
});
}
return response;
},
function (error) {
const originalRequest = error.config;
requestArray.push(originalRequest);
let reqData = "username=" + number + "&password=" + pin + "&grant_type=password" + "&AppType=2" + "&FcmToken=null";
// console.log('error ' , error);
if (error.message === "Request failed with status code 401" || error.statuscode === 401) {
if (!originalRequest._retry) {
originalRequest._retry = true;
return axios({
method: 'post',
url: '/api/login',
data: reqData,
headers: {
"Content-Type": "application/x-www-form-urlencoded",
"Cache-Control": "no-cache",
}
})
.then(res => {
let response = res.data;
console.log('successfull Login', response)
if (res.data.StatusCode == 200) {
AsyncStorage.setItem('userToken', JSON.stringify(response.access_token));
stateFunctions.UserId = response.UserId;
stateFunctions.CustomerContactID = response.CustomerContactID;
let obj = {
access_token: response.access_token,
token_type: response.token_type,
expires_in: response.expires_in,
UserId: response.UserId,
CustomerContactID: response.CustomerContactID,
Mobile: response.Mobile,
StatusCode: response.StatusCode
}
AsyncStorage.setItem('logindetail', JSON.stringify(obj));
if (requestArray.length != 0) {
requestArray.forEach(x => {
try {
console.log(x, "request Url");
x.headers.Authorization = `Bearer ${response.access_token}`;
x.headers["Content-Type"] = "application/x-www-form-urlencoded";
APIKit.defaults.headers.common["Authorization"] = `Bearer${response.access_token}`;
APIKit(x)
} catch (e) {
console.log(e)
}
});
}
return APIKit(originalRequest);
}
})
.catch(err => {
console.log(err);
});
}
}
return Promise.reject(error);
}
);
export default APIKit;
Home.js
gettingToken = async () => {
let userToken = await AsyncStorage.getItem('userToken');
userToken = JSON.parse(userToken);
await setClientToken(userToken);
}

Axios interceptor request to refresh id token when expired in VueJs

I want to use axios interceptor before every axios call to pass idToken as authorization header with all the axios calls and I want to refresh the idToken if it has expired before any call.
I am using the following code:
axios.interceptors.request.use(function(config) {
var idToken = getIdToken()
var refreshToken = {
"refreshToken" : getRefreshToken()
}
if(isTokenExpired(idToken)){
console.log("==============Reloading")
refresh(refreshToken).then(response=>{
setIdToken(response.idToken)
setAccessToken(response.accessToken)
})
idToken = getIdToken()
config.headers.Authorization = `${idToken}`;
}
else{
config.headers.Authorization = `${idToken}`;
}
return config;
}, function(err) {
return Promise.reject(err);
});
It works fine till the time idToken is valid. When the idToken expires it gets in an infinite loop and the page hangs. Please help me with this. The refresh() which call the refresh API looks like this:
function refresh(refreshToken) {
const url = `${BASE_URL}/user/refresh`;
return axios.post(url,JSON.stringify(refreshToken))
.then(response =>response.data.data)
.catch(e => {
console.log(e);
});
}
I had some similar problem and creating new axios instance to perform refresh token api call resolved the problem (new AXIOS instance is not resolved by defined axios.interceptors.request.use) (of course below code is just a simple example).
Remember to save original request and process it after token has been refreshed:
F.ex my http-common.js
import axios from 'axios'
const AXIOS = axios.create()
export default AXIOS
...
in App.vue:
axios.interceptors.request.use((config) => {
let originalRequest = config
if (helper.isTokenExpired(this.$store.getters.tokenInfo)) {
return this.refreshToken(this.$store.getters.jwt).then((response) => {
localStorage.setItem('token', response.data.token)
originalRequest.headers.Authorization = response.data.token
return Promise.resolve(originalRequest)
})
}
return config
}, (err) => {
return Promise.reject(err)
})
and the refresh token method:
refreshToken (token) {
const payload = {
token: token
}
const headers = {
'Content-Type': 'application/json'
}
return new Promise((resolve, reject) => {
return AXIOS.post('/api/auth/token/refresh/', payload, { headers: headers }).then((response) => {
resolve(response)
}).catch((error) => {
reject(error)
})
})
}
}

Categories

Resources