Getting access token with the refresh token after expiration(JWT) - reactjs

I get 401 error after a while when the page is reloaded, I figured it could be because the access token is expired. How do I set a new token with my refresh token? The below function runs every time the user visits a new page or refreshes the page. But it doesn't seem to work.
export async function currentAccount() {
if (store.get('refreshToken')) {
const query = {
grant_type: 'refresh_token',
companyId: store.get('lastCompanyId'),
refresh_token: store.get('refreshToken'),
}
const queryString = new URLSearchParams(query).toString()
const actionUrl = `${REACT_APP_SERVER_URL}/login?${queryString}`
return apiClient
.post(actionUrl, { auth: 'basic' })
.then(async response => {
if (response) {
const { access_token: accessToken } = response.data
store.set('accessToken', accessToken)
return response.data
}
return false
})
.catch(err => {
console.log('error', err)
store.clearAll()
})
}
return false
}
Login sets the access tokens
export async function login(email, password) {
const query = {
grant_type: 'password',
username: email,
password,
}
const queryString = new URLSearchParams(query).toString()
const actionUrl = `${REACT_APP_SERVER_URL}/login?${queryString}`
return apiClient
.post(actionUrl, { auth: 'basic' })
.then(async response => {
if (response) {
const {
data: {
access_token: accessToken,
refresh_token: refreshToken,
},
} = response
const decoded = jsonwebtoken.decode(accessToken)
response.data.authUser = decoded.authUser
const { userId, profileId, companyId } = decoded.authUser
if (accessToken) {
store.set('accessToken', accessToken)
store.set('refreshToken', refreshToken)
}
return response.data
}
return false
})
.catch(err => console.log(err))
}
saga users.js
export function* LOAD_CURRENT_ACCOUNT() {
yield put({
type: 'user/SET_STATE',
payload: {
loading: true,
},
})
const { authProvider } = yield select((state) => state.settings)
const response = yield call(mapAuthProviders[authProvider].currentAccount)
if (response) {
const decoded = jsonwebtoken.decode(response.access_token)
response.authUser = decoded.authUser
yield store.set('id', id)
try {
const user = yield call(LoadUserProfile)
if (user) {
const { company } = user
yield put({
type: 'user/SET_STATE',
payload: {
...user,
preferredDateFormat: user.preferredDateFormat || 'DD/MM/YYYY',
userId,
id,
},
})
}
} catch (error) {
}
}else{
store.set('refreshToken', response.refreshToken)
}
yield put({
type: 'user/SET_STATE',
payload: {
loading: false,
},
})
}

You can get a new access token with your refresh token using interceptors. Intercept and check for response status code 401, and get a new access token with your refresh token and add the new access token to the header.
Example:
return apiClient
.post(actionUrl, { auth: 'basic' })
.then(async response => {
if (response) { // check for the status code 401 and make call with refresh token to get new access token and set in the auth header
const { access_token: accessToken } = response.data
store.set('accessToken', accessToken)
return response.data
}
return false
});
Simple Interceptor example,
axios.interceptors.request.use(req => {
req.headers.authorization = 'token';
return req;
});
Interceptor example for 401
axios.interceptors.response.use(response => response, error => {
if (error.response.status === 401) {
// Fetch new access token with your refresh token
// set the auth header with the new access token fetched
}
});
There are several good posts on Interceptors usage for getting a new access token with your refresh token
https://thedutchlab.com/blog/using-axios-interceptors-for-refreshing-your-api-token
https://medium.com/swlh/handling-access-and-refresh-tokens-using-axios-interceptors-3970b601a5da
Automating access token refreshing via interceptors in axios
https://stackoverflow.com/a/52737325/8370370

The above answer is good. But I found below method is better than that also using Axios Interceptors and "jwt-decode". Give it a try. (I'm using session storage for this example. You can use your own way to store the tokens securely)
Methodology
Login to get an access token and long live refresh token and then store them securely.
Create an axios instance to check the access token expiration with "jwt-decode". Then add the access token into the request if there is a valid access token, or else request a new access token using the stored refresh token and then apply the new access token into the request.
Login:
import axios from 'axios'
const handleLogin = async (login) => {
await axios
.post('/api/login', login, {
headers: {
'Content-Type': 'application/json'
}
})
.then(async response => {
sessionStorage.setItem('accessToken', response.data.accessToken)
sessionStorage.setItem('refreshToken', response.data.refreshToken)
})
.catch(err => {
if (errorCallback) errorCallback(err)
})
}
Create axios instance:
import axios from 'axios'
import jwt_decode from 'jwt-decode'
import dayjs from 'dayjs'
const axiosInstance = axios.create({
headers: { 'Content-Type': 'application/json' }
})
axiosInstance.interceptors.request.use(async req => {
const accessToken = sessionStorage.getItem('accessToken') ? sessionStorage.getItem('accessToken') : null
if (accessToken) {
req.headers.Authorization = `Bearer ${accessToken}`
}
const tokenData = jwt_decode(accessToken)
const isExpired = dayjs.unix(tokenData.exp).diff(dayjs()) < 1
if (!isExpired) return req
const refreshToken = sessionStorage.getItem('refreshToken')
const response = await axios.post('/api/refresh', { refreshToken }, {
headers: {
'Content-Type': 'application/json'
}
})
req.headers.Authorization = `Bearer ${response.data.accessToken}`
sessionStorage.setItem('accessToken', response.data.accessToken)
return req
})
export default axiosInstance
Use axios instance in all the requests (Redux Toolkit Example):
import { createSlice, createAsyncThunk } from '#reduxjs/toolkit'
// Import axiosInstance
import axiosInstance from 'src/utils/axiosInstance'
export const getItems = createAsyncThunk(
'appItems/getItems',
async (args, { rejectedWithValue }) => {
try {
const response = await axiosInstance.get('/api/items')
return response.data
} catch ({ response }) {
return rejectedWithValue({ code: response.status, ...response.data })
}
}
)

Related

how to pass token to local storage with axios

I created an Axios instance to set up the baseURL and the headers. The header also needs to contain the token for authorization.
export const instance = axios.create({
baseURL: import.meta.env.VITE_API_URL,
headers: {
Authorization: `Bearer ${localStorage.getItem(LOCAL_STORAGE_API_KEY)}`
},
validateStatus: () => true
});
when the user logs in, I call an API to get some data related to the user using useQuery. When I log in, I try to store the token in local storage, but I think I'm doing something wrong and I get an error from the backend.
export const LOCAL_STORAGE_API_KEY = 'token';
import { instance } from './ApiProvider';
import { LOCAL_STORAGE_API_KEY } from '#/helpers/constants';
export const loginActions = async ({ email, password }) => {
const response = instance
.post('/api/v1/Auth/Login', {
user: {
email: email,
password: password
}
})
.then((data) => {
instance.defaults.headers.post[
'Authorization'
] = `Bearer ${localStorage.getItem('LOCAL_STORAGE_API_KEY')}`;
return data;
});
return response;
};
The problem is that instance is created before you have the auth header value available and hence on subsequent call it will pass the value as undefined.
You can use axios interceptors for this task.
instance.interceptors.request.use(
function(config) {
const token = localStorage.getItem("LOCAL_STORAGE_API_KEY");
if (token) {
config.headers["Authorization"] = 'Bearer ' + token;
}
return config;
},
function(error) {
return Promise.reject(error);
}
);

Resending a request that was made with an expired token is leading to status pending in developer tools

I have a react application where I am trying to implement JWT.
I am using the axios interceptor where I catch status 401 returned by the server due to expired token, send the refresh token to server, receive the new access token in the client and then resend the original failed request.
The problem I am facing is that, when I resend the original failed request, the status appears as pending forever in the developer tools, network tab. The original failed request is a POST request, when I checked the database it was updated. So why is it showing pending status in the developer tools ?
Here is my axios interceptor code
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
if (errorStatus === 401){ // status 401 is used when token is expired
let cookies = document.cookie
let refresh = cookies.split("refresh=")[1].split(';')[0]
if(!sendRefreshToken(refresh, error)) {
store.dispatch(removeAuth({isLoggedIn: false}));
localStorage.setItem('token', '');
document.cookie = "refresh=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;";
}
}
return error
}
);
}
function sendRefreshToken(refreshToken, error) {
let result = api.post('/refresh', {
refreshToken: refreshToken
})
.then(response => {
if (response.data.success && response.data.message === "new access token set") {
localStorage.setItem('token', response.data.newToken)
api({ // Here I am resending the failed request.
method: error.response.config.method,
url: error.response.config.url,
data: JSON.parse(error.response.config.data)
}).then(response => {
console.log(response)
return true
})
.catch(error => {
console.log(error)
return false
})
}
})
.catch(error => {
console.log(error)
return false
})
return result
}
createAxiosResponseInterceptor(api);
export default api;
Please let me know if you find anything wrong with the code. Let me know if this is the right way to do it. Open to offer more bounty points.
Consider this article for reference.
https://medium.com/swlh/handling-access-and-refresh-tokens-using-axios-interceptors-3970b601a5da
import axios from 'axios'
// import refreshToken from '../src/Store/refreshToken'
import { store } from '../src/index'
import { removeAuth } from '../src/Store/actions/authAction'
const api = axios.create({
baseURL: process.env.REACT_APP_SERVER
})
function createAxiosResponseInterceptor(axiosInstance) {
axiosInstance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
if (token){
config.headers.Authorization = token;
}
return config
}
)
axiosInstance.interceptors.response.use(
response => {
return response;
},
error => {
var errorStatus = error.response.status;
const originalRequest = error.config;
if (
error.response.status === 401 &&
!originalRequest._retry
) {
originalRequest._retry = true;
return api
.post('/refresh', {
refreshToken: getRefreshToken()
})
.then((jsonRefreshResponse) => {
if (jsonRefreshResponse.status === 200) {
// 1) put token to LocalStorage
saveRefreshToken(
jsonRefreshResponse.data.refreshToken
);
// 2) Change Authorization header
const newAccessToken = getJwtToken();
setAuthHeader(newAccessToken);
// 3) return originalRequest object with Axios.
// error.response.config.headers[
// "Authorization"
// ] = `Bearer ${newAccessToken}`;
setAuthHeader(newAccessToken)
return axios(error.response.config);
}
})
.catch((err) => {
console.warn(err);
})
}
if (error.config) {
console.log(error.config);
return Promise.reject();
}
}
);
}
export const setAuthHeader = (token) => {
api.defaults.headers.common["Authorization"] = `Bearer ${token}`;
};
createAxiosResponseInterceptor(api);
export default api;
//These methods could be in separate service class
const getJwtToken=()=> {
return localStorage.getItem("token");
}
const getRefreshToken=() =>{
return localStorage.getItem("refreshToken");
}
const saveJwtToken=(token)=> {
localStorage.removeItem("token");
localStorage.setItem("token", token);
}
const saveRefreshToken=(refreshToken)=> {
localStorage.setItem("refreshToken", refreshToken);
}

How to Resolve 401 unAuthorized error even though Tokens and everything is set? and postman returns no issue at all while testing -MERN Stack

i am new to react and I've been trying to make this api work,(following you tube tutorial), when i test the routes via postman the response i receive is 'OK-working' but then when i try to login from my react app, it turns to unauthorized, i don't know how to deal with this *** i have done everything i could possibily upto my understanding ***, I've explored as much stack Overflow as i could as well as Googled it but still not found something understandable enough.
p.s this is very critical issue for me as this is part of my web Class at college and it holds a great deal of grades!
Attaching the link to github directory as well in case you need it :
https://github.com/nescafestar/twitter-clone
this is my AuthActions.js file
```
//authAction.js
import axios from 'axios';
import {GET_ERRORS, SET_CURRENT_USER} from '../constants';
import setAuthHeader from '../utils/setAuthHeader'
export const registerUser = (userData, history) => dispatch => {
axios.post('http://localhost:5000/api/users/register', userData,{
headers: {
'Authorization': 'Bearer'+ localStorage.getItem('jwtToken')
}
})
.then(res => {
console.log('----> registering')
const { token } = res.data
localStorage.setItem('jwtToken', token)
if(token){
axios.defaults.headers.common['Authorization']='Bearer : '+ token
}
history.push('/')})
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}))
}
export const loginUser = (userData) => dispatch => {
axios.post('http://localhost:5000/api/users/login',userData,{
headers: {
'Authorization': 'Bearer : ' +localStorage.getItem('jwtToken')
}
})
.then(res => {
// console.log(userData)
const { token } = res.data
localStorage.setItem('jwtToken', token)
if(token){
axios.defaults.headers.common['Authorization']='Bearer : ' +token
}
console.log('---> hit dispatch')
dispatch(getCurrentUser())
})
.catch(err => {
// console.log(err);
// dispatch({
// type: GET_ERRORS,
// payload: err.response.data
// })
})
}
export const getCurrentUser = () => dispatch => {
axios.get('http://localhost:5000/api/users',{
headers: {
'Authorization': 'Bearer : ' +localStorage.getItem('jwtToken')
}
})
.then(res => dispatch(setCurrentUser(res.data)))
.catch(err => dispatch({
type: GET_ERRORS,
payload: err.response.data
}))
}
export const setCurrentUser = (data) => {
console.log('----> setting user!')
return {
type: SET_CURRENT_USER,
payload: data
}
}
export const logoutUser=()=>dispatch=>{
localStorage.removeItem('jwtToken')
setAuthHeader()
dispatch(setCurrentUser())
}
```
this is the set Auth Header one:
```
import axios from 'axios'
// const jsonwebtoken=require('jsonwebtoken')
export default function(token){
console.log(token)
if(token){
console.log('Token has been set-properly')
return axios.defaults.headers.common['Authorization']='Bearer'+ token
}else{
return axios.defaults.headers.common['Authorization']=null
}
}
```
and lastly this is my POST.js file which is also returning the same issue of being unAuthorized
import axios from 'axios'
// const jsonwebtoken=require('jsonwebtoken')
export default function(token){
console.log(token)
if(token){
console.log('Token has been set-properly')
return axios.defaults.headers.common['Authorization']='Bearer'+ token
}else{
return axios.defaults.headers.common['Authorization']=null
}
}
``` ```
////////////////////////*** ROUTER ***
``` ```
// also the Routes/User.js file in case someone wants to know about it as well
const router = require("express").Router();
const User = require("../models/User");
const jwt=require('jsonwebtoken');
const passport = require("passport");
//validation
const validateRegisterInput = require("../validation/register");
const validateLoginInput = require("../validation/login");
//handling password hasing
const bcrypt = require("bcryptjs");
// const utils = require("../utils/utils");
//routing requests
//handling post request
router.route("/register").post((req, res) => {
const { isValid, errors } = validateRegisterInput(req.body);
if (!isValid) {
return res.status(404).json(errors);
}
//test arguments
// return res.send('OKKKK!!');
//register user is it does not exist in db
User.findOne({ email: req.body.email }).then((user) => {
if (user) {
errors.email = "Email already Registered!";
return res.status(404).json(errors);
}
bcrypt.genSalt(10, function (err, salt) {
bcrypt.hash(req.body.password, salt, function (err, hash) {
//adding new User to DB is Not Exist
const newMember = new User({
username: req.body.username,
email: req.body.email,
password: hash,
});
newMember
.save()
.then((newMember) => res.json(newMember))
.catch((err) => console.log(err));
});
});
});
}); //register route ends
//Login Route
router.route('/login')
.post((req, res) => {
const { errors, isValid } = validateLoginInput(req.body)
if (!isValid) {
return res.status(404).json(errors)
}
User.findOne({ email: req.body.email })
.then(user => {
if (!user) {
errors.email = 'User not found/not exist'
return res.status(404).json(errors)
} else {
bcrypt.compare(req.body.password, user.password)
.then(isMatch => {
if (isMatch) {
const token = jwt.sign({ id: user._id }, process.env.SECRET, { expiresIn: '1d' }, function (err, token) {
return res.json({
success: true,
token: token
})
})
} else {
errors.password = 'Password is incorrect'
return res.status(404).json(errors)
}
})
}
})
})
//returning route for logged in user
router.route('/')
.get( passport.authenticate('jwt', { session: false }),(req, res) => {
console.log('here! in home redirect')
res.json({
_id: req.user._id,
email: req.user.email,
username: req.user.username,
followers: req.user.followers,
following: req.user.following
})
})
module.exports = router;
You are not populating the Authorization header consistently or correctly. Assume your jwtToken value is AAAA for simplicity. In some cases you send BearerAAAAAA, in others you send Bearer : AAAA. The standard should be to use the string "Bearer", followed by one space, followed by the base64 encoding of the token (Bearer AAAA). See the RFC for more information: https://www.rfc-editor.org/rfc/rfc6750
When setting the header, you could use something like this:
const token = localStorage.getItem('jwtToken') //Or however you choose to get it
const headers = {
Authorization: `Bearer ${token}`
}

How to use refresh token in reactjs

I am working on a admin app in reactjs which uses redux for state management. In my app when user log in it gets access_token and refresh _token. access_token which gets expired after 5 min. after 5 min token becomes invalid to make any api endpoint request.
I want to know how am I suppose to use this refresh_token to update my access_token which is stored in localStorage of the browser. I had no idea about this refresh_token before this. This is how I make login request and save my access_token and refresh_token.
authentication:
export const Authentication = async(payload) =>{
try{
const response = await fetch(`${generalConfig.baseURL}/oauth/token`, {
method: 'POST',
cache: 'no-cache',
headers: {
'Authorization': `Basic ${btoa("topseller:topseller")}`,
'Accept': '*/*',
// 'Content-Type': 'multipart/form-data',
'accept-encoding': 'gzip, deflate',
},
body: payload
})
.then((response)=>{
console.log(response)
return response.json()
},err=>{
console.log(err,'############')
})
console.log(response,'#########')
return response;
}catch(err){
console.log(err,'############')
}
}
authentication.action:
export const getAccessToken = (dataToSend) => async (dispatch) => {
try {
var formData = ConvertToFormData(dataToSend);
const authResponse = await Authentication(formData);
<-- See above about this Authentication function
const response = await fetch("http://api.smartocart.com/userType", {
method: "GET",
cache: "no-cache",
headers: {
Authorization: `Bearer ${authResponse.access_token}`,
},
});
const payload = await response.json();
if (payload.status === "admin") {
SaveToLocalStorage("access_token", authResponse.access_token);
SaveToLocalStorage("refresh_token", authResponse.refresh_token);
dispatch({
type: GET_ACCESS_TOKEN,
payload: {
access_token: authResponse.access_token,
refresh_token: authResponse.refresh_token,
},
});
} else {
dispatch({
type: ERROR_ACCESS_TOKEN,
buttonPressed: true,
});
}
} catch (exception) {
console.log("Log In again");
}
};
I did read about this in some of the blog post but i did get this. https://nmajor.com/posts/access-and-refresh-token-handling-with-redux
Any help would be highly appreciated.
You can add token expiry time validation on app.js so if you reload you application or move to next page or if you make api call it will check token expiry time validation always if token expired it will make call to fetch update token
check below example : i gave example with react axios
axios.interceptors.request.use(async (config) => {
const expireAt = localStorage.getItem('expiresAt');
let token = localStorage.getItem('authToken');
if (dayjs(expireAt).diff(dayjs()) < 1) {
const data = onGetForcedToken();
token = typeof data === 'string' ? data : await data();
}
// setting updated token
localStorage.setItem('authToken', token);
return config;
}, (err) => {
console.log("error in getting ",err)
});
If you are using Axios, you can intercept a request with the help of interceptor and call api to get a new token in case token got expired.
Another approach to get a new token is by periodically calling api after certain interval before the token gets expired.
For example in App.js
// Get new Token
const getNewUserToken = async () => {
const submitUrl = apiRoot + "/v1/user/refreshtoken";
try {
const res = await fetch(submitUrl, {
method: "GET",
headers: {
token: localStorage.getItem("token"),
"Content-Type": "application/json",
},
});
if (res.status === 200) {
const data = await res.json();
localStorage.setItem("token", data.token);
} else {
// New token didnt received.Remove the previous token and user
localStorage.removeItem("token");
localStorage.removeItem("user");
setUser({});
navigate("/");
}
} catch (err) {
console.log(err);
}
};
const intervalRef = useRef();
const getToken = useCallback(() => {
// Get new token if and only if existing token is available
if (localStorage.getItem("token") != null) {
getNewUserToken();
}
}, []);
// Trigger API to get a new token before token gets expired.
useEffect(() => {
const interval = setInterval(() => getToken(), 1000 * 60 * 6); // 6 minutes interval as our token will expire after 7 minutes.
intervalRef.current = interval;
return () => clearInterval(interval);
}, [getToken]);
Hope this will help you to automatically refresh the token without forcing user to login.

In my header I have the token but it is saying no token as if there is no token. but when it try it on postman it works fine. what is the problem here

this is my action code:
export const accept = (clinicianId, duration) => (dispatch, getState) => {
axios
.post(
"http://localhost:5000/api/patient/authAccess",
clinicianId,
duration,
tokenConfig(getState)
)
.then(res =>
dispatch({
type: ACCESS,
payload: res.data
})
)
.catch(err => {
dispatch(
returnErrors(err.response.data, err.response.status, "ACCESS_FAIL")
);
});
};
this is how I set the token and also add it to my header:
export const tokenConfig = getState => {
const token = getState().auth.token;
const config = {
headers: {
"Content-type": "application/json"
}
};
if (token) {
config.headers["x-auth-token"] = token;
}
return config;
};
and this is my api where the token will go to the auth function and authorize the user:
const { clinicianId, duration } = req.body;
jwt.sign(
{ id: clinicianId },
config.get("jwtSecretAccess"),
{ expiresIn: duration },
(err, token) => {
if (err) throw err;
res.json({
token
});
}
);
when I checked the value of the token there is one i think the api can't read it? I don't know what to do now.
You can use Axios interceptor as you can add a header for each http request
https://dev.to/teroauralinna/global-http-request-and-response-handling-with-the-axios-interceptor-30ae

Resources