Axios interceptor request to refresh id token when expired in VueJs - javascript

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

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

Correct way to use axios interceptors

I want to add jwt token to my axiosinstance in Login.js but it is giving me error
IDX12729: Unable to decode the header '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' as Base64Url encoded ...]
Here is my code:
Login.js
const printValues = e =>{
axiosInstance.post('/auth', data)
.then(res =>{
console.log("adding token");
const config = axiosInstance.interceptors.request.use(function (config) {
config.headers.Authorization = res.data.token;
return config;
});
axiosInstance.get('/User/GetUserByID/0', config)
.then(res =>{
//set user details
})
.catch(err =>{
console.log(err);
})
}
use doesn't return a config for you to pass into requests. As long as you are using the same instance, the config would get altered.
axiosInstance.interceptors.request.use(function (config) {
config.headers.Authorization = res.data.token;
return config;
});
axiosInstance.get('/User/GetUserByID/0')
.then(res =>{
//set user details
})
.catch(err =>{
console.log(err);
})
First, don't define interceptors within a response handler. That means you'll be adding an interceptor every time you make that request.
Typically you would keep your token state and interceptor separate from other application code. Wherever you created your axiosInstance is a good candidate.
For example...
import axios from "axios"
const axiosInstance = axios.create({
// ..
})
const token = null // initial state
axiosInstance.interceptors.request.use(async config => {
if (!token) {
// note, use a separate axios instance for this request
const { data } = await axios.post("/auth", dataFromSomewhere)
token = data.token
}
config.headers.Authorization = `Bearer ${token}` // you may need "Bearer" here
return config
})
Now you can use axiosInstance to make requests and it will transparently resolve your authorisation token if required before continuing.
const printValues = e => {
axiosInstance.get("/User/GetUserByID/0")
.then(res =>{
//set user details
})
.catch(err =>{
console.log(err);
})
}

error retrieving user Id token assigned by firebase in the client side

I am using JWT based authentication using firebase Admin SDK in express js.
according to the sign in with custom token when we sign the user with the function signInWithCustomToken(token) firebase sets a user-id token for that user.
according to retrieve id tokens
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true).then(function(idToken) {
// Send token to your backend via HTTPS
// ...
}).catch(function(error) {
// Handle error
});
we can get the token if the user is logged in
but executing this I get error that getIdToken value is null.
i changed the code to
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
console.log(error)
});
const userToken = await token;
const getData = async (userToken) => {
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: userToke})
})
const data = await response.json()
console.log(responnse)
}
}
getUser();
but still receiving the same error
I looked up for some solutions and found similar answers to the question one of which I implemented was solution
it used onAuthStateChanged method and I am using
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/7.14.1/firebase-auth.js"></script>
in cdn but now am getting
Uncaught (in promise) TypeError: firebase.auth.onAuthStateChanged is not a function
at profile:40
at new Promise (<anonymous>)
at getIdTokenRefreshed (profile:37)
at profile:50
I changed the above code to this
firebase.initializeApp(firebaseConfig);
const getIdTokenRefreshed = async () => {
return new Promise(async (resolve, reject) => {
const unsubscribe = await firebase
.auth
.onAuthStateChanged(async user => {
unsubscribe()
const refreshedToken = await user
.getIdToken(true)
.catch(err => console.error(err))
resolve(refreshedToken)
console.log(refreshedToken)
}, reject)
});
}
getIdTokenRefreshed();
still getting the second error where onAuthStateChanged is not defined
how do I retrieve the user id token?
UPDATE
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
after implementing the above code this is the error
await is only valid in async functions and the top level bodies of modules
First, I'd recommend updating Firebase SDK to latest version which is 8.9.1 at the time of writing this.
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/8.9.1/firebase-auth.js"></script>
If you take a look at onAuthStateChanged part in the documentation, it should be:
firebase.auth().onAuthStateChanged(...)
// ^^
// not firebase.auth.onAuthStateChanged
The onAuthStateChanged won't be triggered unless you call the getIdTokenRefreshed function. You can simply refactor that function to:
const getIdTokenRefreshed = async () => {
try {
const user = firebase.auth().currentUser
if (user) {
const token = await user.getIdToken(true)
console.log(`Token: ${token}`)
return token
} else {
console.log("No user is logged in")
}
} catch (e) {
console.log(`Something went wrong: ${e}`)
}
}
Lastly, the variable name is userToken but in request body it is body: JSON.stringify({idToken: userToke}) and you don't need an await before a variable name. Try refactoring the getUser function to:
//const token = await firebase.auth().currentUser.getIdToken(/* forceRefresh */true).catch(function(error) {
// console.log(error)
//});
//const userToken = await token;
const getUser = async () => {
const token = await firebase.auth().currentUser.getIdToken(true)
const response = await fetch('/getUser', {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({idToken: token})
})
const data = await response.json()
console.log(data)
return data
}
getUser().then(data => {
console.log("Data received")
})

How can I set my jwt as a cookie to prevent having to login on refresh?

I am attempting to store a JWT as a cookie to prevent my Axios call creating a new one each time and making me login every time the app is refreshed
I think I am on the right path using JS-Cookie and setting the cookie to my JWT provided by the API. However I am still redirected on login every refresh. How can I keep the authToken as my original JWT token?
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
let authToken = null;
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
if (Cookies.get('token') == null) {
authToken = res.data.token;
} else {
Cookies.set('token', res.data.token);
authToken = Cookies.get('token');
}
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
I have also tried this:
import axiosAPI from 'axios';
import Cookies from 'js-cookie';
const authToken = Cookies.get('token');
const axios = axiosAPI.create({
baseURL: `${baseURL}`
});
// User login
export const loginUser = (data) => {
return new Promise((resolve, reject) => {
axios.post(`${baseURL}/jwt-auth/v1/token`, data)
.then((res) => {
if (Cookies.get('token') === null) {
Cookies.set('token', res.data.token);
}
// Adds the token to the header
axios.defaults.headers.common.Authorization = `Bearer ${authToken}`;
resolve(res.data);
})
.catch((error) => {
reject(error);
});
});
};
which fails to log me in altogether

Async and localStorage not working properly

So I'm using React with React-Router.
I have a onEnter hook which checks if the user is authenticates yes/no and executes the desired action.
export function requireAuth(nextState, replaceState) {
if (!isAuthenticated()) {
if (!Storage.get('token')) replaceState(null, '/login');
return delegate().then(() => replaceState(null, nextState.location.pathname));
}
if (nextState.location.pathname !== nextState.location.pathname) {
return replaceState(null, nextState.location.pathname);
}
}
When the token is expired I call a delegate function which looks like:
export function delegate() {
const refreshToken = Storage.getJSON('token', 'refresh_token');
return getData(endpoint)
.then(res => {
Storage.set('token', JSON.stringify({
access_token: res.data.access_token,
refresh_token: refreshToken,
}));
});
}
The delegate function indeed refresh the tokens in the localStorage. But the requests after the replaceState are not using the updated token, but the previous one. I think this is a async issue, someone knows more about this?
Edit: The function where I use the token:
function callApi(method, endpoint, data) {
return new Promise((resolve, reject) => {
let headers = {
'Accept': 'application/json',
'X-API-Token': Storage.getJSON('token', 'access_token'),
};
const body = stringifyIfNeeded(data);
const options = { method, headers, body };
return fetch(endpoint, options)
.then(response =>
response.json().then(json => ({ json, response }))
).then(({ json, response }) => {
if (!response.ok) {
reject({ json, response });
}
resolve(json);
}).catch((error, response) => {
reject({ error, response });
});
});
}

Categories

Resources