Axios multiple request on interceptor - javascript

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

Related

How to refresh token in axios?

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

HTTP function times out when subscribing an FCM token to a topic in Cloud Function

Minimum reproducible code:
index.ts:
import * as admin from "firebase-admin"
import fetch, { Headers } from "node-fetch";
interface BarPayload {
topic: string,
token: string,
}
exports.bar = functions.https.onCall(async (data, context) => {
if (data != null) {
const payload: BarPayload = {
topic: data.topic,
token: data.token,
}
const url = `https://${location}-${project}.cloudfunctions.net/subscribeToTopic`
await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
topic: payload.topic,
token: payload.token,
}),
})
}
return null;
});
export const subscribeToTopic = functions.https.onRequest(async (req, res) => {
const payload = req.body as BarPayload;
fetch('https://iid.googleapis.com/iid/v1/' + payload.token + '/rel/topics/' + payload.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
}).then(response => {
if (response.status < 200 || response.status >= 400) {
res.sendStatus(299)
}
}).catch(error => {
console.error(error);
res.sendStatus(299)
})
return Promise.resolve();
})
I'm running bar in Flutter and I see the timeout error in Logs Explorer:
textPayload: "Function execution took 60051 ms. Finished with status: timeout"
But if I change my subscribeToTopic from HTTP function to a callable function, then it works fine. For example:
exports.subscribeToTopic = functions.https.onCall(async (data, context) => {
fetch('https://iid.googleapis.com/iid/v1/' + data.token + '/rel/topics/' + data.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
}).then(response => {
if (response.status < 200 || response.status >= 400) {
console.log('Error = ' + response.error);
}
}).catch(error => {
console.error(error);
})
return null;
});
(I know I'm making some trivial mistake, and I'm new to Typescript. Any help would be appreciated :)
You should not do return Promise.resolve(); in the HTTPS Cloud Function:
HTTPS Cloud Functions shall be terminated with with send(), redirect() or end();
return Promise.resolve(); is executed before the asynchronous call to fetch is complete.
The following should do the trick (untested):
export const subscribeToTopic = functions.https.onRequest(async (req, res) => {
try {
const payload = req.body as BarPayload;
const response = await fetch('https://iid.googleapis.com/iid/v1/' + payload.token + '/rel/topics/' + payload.topic, {
method: 'POST',
headers: new Headers({
'Authorization': 'key=AA...Wp9',
'Content-Type': 'application/json'
})
});
if(response.status < 200 || response.status >= 400) {
res.status(299).send();
}
} catch (error) {
res.status(400).send();
}
})
However I don't understand why you separate your business logic in two Cloud Functions. Why don't you directly fetch https://iid.googleapis.com within the bar Callable Cloud Function?

Issues with returning values from nested axios call in ReactJs

The below function does not return any value when the lenght of the output from the first call. I have given comment in the line of code that is creating issues in the below snippet. Can you please advise what is the issue with my code?
async createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
const response = await axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length===0){
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
})
}else{
console.log(response.data); // Printing the proper results
return response.data, // not returning any results to next then.Return statement not working
}
}
}).then((response) => {
return{
items: response.data, // returning proper results for if statement but failing for else condition
}
})
.catch((error) => (console.log( JSON.stringify(error)) ));
}
///calling the nested axios call
const ret = MyProfileRepository.createProfileByUserIDNew(
data,
user.email
);
ret.then(function (response) {
console.log(response); //Printing 'undefined'
This.setState({ buyerProfileId: response.items.id });
});
Your createProfileByUserIDNew is an async function, meaning you need a return statement:
async function createProfileByUserIDNew() {
const AuthStr = 'Bearer ' + getToken();
const response = await axios.get(...).etc;
return response;
}
Alternatively, you can also directly return a promise:
async function createProfileByUserIDNew() {
const AuthStr = 'Bearer ' + getToken();
return axios.get(...).etc;
}
You should have only one .then callback function. Here is the correct implementation
async createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
const response = await axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length===0){
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
}).then((response) => {
return{
items: response.data, // returning proper results.working as expected
}
})
.catch((error) => (console.log( JSON.stringify(error)) ));
}
}else{
console.log(response.data[0]); // Printing the proper results
return{
items: response.data[0], // not returning any results.Return statement not working
}
}
})
But you can improve your conditions but for the sake of argument, this code will work.
The problem is that when you return from your first then function, the promise falls through to the second then function. The else condition of your first function returns an object shaped as { items: data } while the second then function is expecting a response object, not an object w/ an items key.
You could handle this in a few ways:
You're defining an async function, but you're not doing anything asynchronously (in terms of ES6 syntax). You could convert your whole function to this syntax to async/await properly within your conditional statements.
You could convert your first then to an async function, then asynchronously fetch the second request from there.
You could change the structure of what you return in the else condition so that it looks like a response object, and can be parsed properly in the secondary then function.
In addition, you are not returning the response promise.
Solution 1
async createProfileByUserIDNew(data, email) {
const AuthStr = 'Bearer ' + getToken();
try {
let response = axios.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
});
if (response.data.length === 0) {
response = await axios.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
}
return {
items: response.data[0],
};
} catch (error) {
console.log(JSON.stringify(error));
}
}
Solution 2
createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
return axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then(async (response) => {
if (response.data.length === 0) {
response = await axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
}
return {
items: response.data[0],
};
})
.catch((error) => {
console.log(JSON.stringify(error));
});
}
Solution 3
createProfileByUserIDNew(data,email) {
const AuthStr = 'Bearer ' + getToken();
return axios
.get(`${baseUrl}/profiles?email=${email}`, {
headers: { Authorization: AuthStr },
})
.then((response) => {
if (response.data.length === 0) {
return axios
.post(`${baseUrl}/buyer-profiles`, data, {
headers: { Authorization: AuthStr },
});
} else {
return {
response: {
data: {
items: response.data[0],
},
},
};
}
}).then((response) => {
return {
items: response.data,
};
})
.catch((error) => {
console.log(JSON.stringify(error));
});
}

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

nested axios call with await in vue js

i am new to async and i have an action in vuex that get user's info and i put that in promise and i want to use this method in then block of axios call for login with await because data of user info is important for me
my problem is i cant use await in then block and error says the error says await can only be use in async function
this flow is correct and what is correct way?
store.js:
actions: {
loadUserInfo({commit}){
const headers = {
'Content-Type': 'application/json',
// 'Authorization': localStorage.getItem('access_token'),
'Accept': 'application/json'
}
return new Promise(function(resolve, reject) {
axios.get(process.env.VUE_APP_BASE_URL + process.env.VUE_APP_EDIT_INFO,{headers: headers})
.then(response => {
commit('authenticateUser');
commit('setUserInfo', response.data.result);
resolve();
})
.catch(error => {
console.log(error);
reject();
})
});
}
},
Login.vue:
async login () {
let self = this;
axios.post(process.env.VUE_APP_BASE_URL + process.env.VUE_APP_LOGIN,
this.loginInfo
).
then(function (response) {
await this.$store.dispatch('loadUserInfo').then((res)=>{
this.$emit('authenticateUser', true);
this.$emit('registeredIdentification', self.$store.getters.getUsername)
this.$router.push({ name: 'mainBox' })
});
localStorage.setItem('access_token', response.data.result.access_token)
localStorage.setItem('refresh_token', response.data.result.refresh_token)
})
.catch(function (error) {
// let statusCode = error.response.status;
console.log(error);
});
}
try moving the async declaration so it is "inside" the then:
async login () {
...
.then(async function (response) {
await this.$store.dispatch('loadUserInfo').then((res)=>{
...
}
Using then with async/await may be unnecessary:
async login() {
try {
const loginUrl = process.env.VUE_APP_BASE_URL + process.env.VUE_APP_LOGIN;
const { data } = await axios.post(loginUrl, this.loginInfo)
const { access_token: accessToken, refresh_token: refreshToken } = data.result;
await this.$store.dispatch('loadUserInfo');
this.$emit('authenticateUser', true);
this.$emit('registeredIdentification', this.$store.getters.getUsername)
this.$router.push({ name: 'mainBox' })
localStorage.setItem('access_token', accessToken)
localStorage.setItem('refresh_token', refreshToken)
} catch (error) {
console.log(error);
}
}

Categories

Resources