I have an issue with refresh tokens in react - javascript

I have an issue with refresh tokens in react and I am using axios interceptors for calling refresh token I am recieving refresh token from flask APIs and the timer of refresh token is 30sec
This is the frontpage of login:
This is the login page;
After login I am recieving this data from the api:
data reacieved: After 30sec I am recieving this error and I am not able to call refresh tokens again for getting data from api
This is my code:`
This is the code where I am creating axios instance and interceptors for req and res:
import axios from 'axios';
const instance = axios.create({
baseURL: "http://192.168.18.63:5000",
headers: { "Content-Type": "application/json" },
});
instance.interceptors.request.use(config => {
const accessToken = localStorage.getItem('access_token');
if (accessToken) {
config.headers.Authorization = `Bearer ${accessToken}`;
}
return config;
});
instance.interceptors.response.use(
response => {
return response;
},
error => {
// If the access token is expired, try to refresh it
if (error.response.data.status === 403 && error.response.data.msg === 'Access token expired') {
const refreshToken = localStorage.getItem('refresh_token');
// Send a request to the server to refresh the access token
return instance
.post('/AdminRefreshToken', {
refresh_token: refreshToken,
})
.then(response => {
// Save the new access token
localStorage.setItem('access_token', response.data.access_token);
// Retry the original request
const config = error.config;
config.headers.Authorization = `Bearer ${response.data.access_token}`;
return instance(config);
});
}
return Promise.reject(error);
}
);
export default instance;`
This is the code where I am fetching the data from axios instance:
import React, { useState, useEffect } from "react";
import Logout from "./logout";
import api from "./Services/Api";
function MyComponent() {
const [data, setData] = useState("");
`your text`
useEffect(() => {
api
.get("/AdminDetails")
.then((response) => {
console.log(response);
setData(response.data.data);
})
.catch((error) => {
console.log(error);
});
}, []);
return <>
<div>
<h4>email: {data.email}</h4>
<h4>first Name: {data.firstname}</h4>
<h4>last Name: {data.lastname}</h4>
<h4>user Name: {data.username}</h4>
<Logout/>
</div>
</>;
}
export default MyComponent;

Related

Axios: method is called before interceptor

I am trying to update access token using refresh tokens. I am using axios interceptors to call the /refreshendpoint which returns a new access token. I am setting the token and refresh token in local storage. however, when the token is expired, the getAll method is called before the response interceptor. the token does get updated in the local storage after I call the /refresh endpoint in the response interceptor, but in the getAll method, the token that's set in the header is still the previous expired token. When I refresh the page though, the new token is set in the header and it works as expected. when I see the server console, it shows the /patients endpoint is called before the /refresh endpoint. I am calling the getAll method when the component mounts.
axios interceptor code:
import { getFromLS } from "./localStorage";
const baseURL = "http://localhost:4200/api"
const instance = axios.create({
baseURL,
});
instance.interceptors.request.use((request: AxiosRequestConfig) => {
axios.defaults.headers.common["Authorization"] = "";
delete axios.defaults.headers.common["Authorization"];
if (getFromLS("token")) {
if(request.headers) {
request.headers.Authorization = getFromLS("token");
}
}
return request;
});
instance.interceptors.response.use(
(response) => response,
(error) => {
const originalRequest = error.config;
if (
error.response.status === 401 &&
originalRequest.url === `${baseURL}/refresh`
) {
return Promise.reject(error);
}
if (error.response.status === 401 && !originalRequest._retry) {
originalRequest._retry = true;
instance
.post("/refresh", {
body: {
id: Number(getFromLS("user")),
refreshToken: getFromLS("refreshToken"),
},
})
.then((response) => {
const newToken = response.data.data.newAccessToken;
localStorage.setItem("token", newToken);
instance.defaults.headers.common[
"Authorization"
] = `Bearer ${newToken}`;
return instance(originalRequest);
})
}
return Promise.reject(error);
}
);
export default instance;
the getAll method which uses the above axios instance:
get: (url: string) => instance.get<IPatientResponse>(url).then(responseBody)
getAll: (): Promise<IPatientResponse[]> => get("/patients"),

JWT returned invalid signature on axios request reactjs

So im trying to create refreshtoken hook in react.
and nodejs with express as my backend.
my backend code looks like this
exports.refreshToken = (req, res) => {
const oldToken = req.headers.authorization.split(" ")[1]
if(oldToken == null ) return res.status(500).send({message: "Token is empty"})
console.log(myJwt.refreshSecretKey)
console.log(oldToken)
jwt.verify(oldToken, myJwt.refreshSecretKey, (err, user) => {
if(err)
res.status(500).send({
msg: err || "Error on refreshing your token"
})
else res.send({ refreshToken: generateRefreshToken() });
})
};
the problem is when i try this endpoint with Postwoman (chrome extension) its WORK
but when i try with React + axios the server return is
{"msg":{"name":"JsonWebTokenError","message":"invalid signature"}}
here is my react code
import axios from '../api/axios'
import useAuth from './useAuth'
const useRefreshToken = () => {
const Auth = useAuth()
const refresh = async () => {
console.log(Auth.auth.token)
const response = await axios.get("user/refresh", {
withCredentials: true,
headers: {
Authorization: `Bearer ` + Auth.auth.token
}
})
Auth(prev => {
console.log(JSON.stringify(prev))
console.log(response?.data?.refreshToken)
return {...prev, token: response.data.refreshToken}
})
return response.data.refreshToken
}
return refresh
}
export default useRefreshToken
I'm sending the wrong access token.
What I send in react is the first created accessToken. not the refreshAccessToken

How i can set globally auth token in axios? [duplicate]

I have a react/redux application that fetches a token from an api server. After the user authenticates I'd like to make all axios requests have that token as an Authorization header without having to manually attach it to every request in the action. I'm fairly new to react/redux and am not sure on the best approach and am not finding any quality hits on google.
Here is my redux setup:
// actions.js
import axios from 'axios';
export function loginUser(props) {
const url = `https://api.mydomain.com/login/`;
const { email, password } = props;
const request = axios.post(url, { email, password });
return {
type: LOGIN_USER,
payload: request
};
}
export function fetchPages() {
/* here is where I'd like the header to be attached automatically if the user
has logged in */
const request = axios.get(PAGES_URL);
return {
type: FETCH_PAGES,
payload: request
};
}
// reducers.js
const initialState = {
isAuthenticated: false,
token: null
};
export default (state = initialState, action) => {
switch(action.type) {
case LOGIN_USER:
// here is where I believe I should be attaching the header to all axios requests.
return {
token: action.payload.data.key,
isAuthenticated: true
};
case LOGOUT_USER:
// i would remove the header from all axios requests here.
return initialState;
default:
return state;
}
}
My token is stored in redux store under state.session.token.
I'm a bit lost on how to proceed. I've tried making an axios instance in a file in my root directory and update/import that instead of from node_modules but it's not attaching the header when the state changes. Any feedback/ideas are much appreciated, thanks.
There are multiple ways to achieve this. Here, I have explained the two most common approaches.
1. You can use axios interceptors to intercept any requests and add authorization headers.
// Add a request interceptor
axios.interceptors.request.use(function (config) {
const token = store.getState().session.token;
config.headers.Authorization = token;
return config;
});
2. From the documentation of axios you can see there is a mechanism available which allows you to set default header which will be sent with every request you make.
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
So in your case:
axios.defaults.headers.common['Authorization'] = store.getState().session.token;
If you want, you can create a self-executable function which will set authorization header itself when the token is present in the store.
(function() {
String token = store.getState().session.token;
if (token) {
axios.defaults.headers.common['Authorization'] = token;
} else {
axios.defaults.headers.common['Authorization'] = null;
/*if setting null does not remove `Authorization` header then try
delete axios.defaults.headers.common['Authorization'];
*/
}
})();
Now you no longer need to attach token manually to every request. You can place the above function in the file which is guaranteed to be executed every time (e.g: File which contains the routes).
Create instance of axios:
// Default config options
const defaultOptions = {
baseURL: <CHANGE-TO-URL>,
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
Then for any request the token will be select from localStorage and will be added to the request headers.
I'm using the same instance all over the app with this code:
import axios from 'axios';
const fetchClient = () => {
const defaultOptions = {
baseURL: process.env.REACT_APP_API_PATH,
method: 'get',
headers: {
'Content-Type': 'application/json',
},
};
// Create instance
let instance = axios.create(defaultOptions);
// Set the AUTH token for any request
instance.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
return instance;
};
export default fetchClient();
The best solution to me is to create a client service that you'll instantiate with your token an use it to wrap axios.
import axios from 'axios';
const client = (token = null) => {
const defaultOptions = {
headers: {
Authorization: token ? `Token ${token}` : '',
},
};
return {
get: (url, options = {}) => axios.get(url, { ...defaultOptions, ...options }),
post: (url, data, options = {}) => axios.post(url, data, { ...defaultOptions, ...options }),
put: (url, data, options = {}) => axios.put(url, data, { ...defaultOptions, ...options }),
delete: (url, options = {}) => axios.delete(url, { ...defaultOptions, ...options }),
};
};
const request = client('MY SECRET TOKEN');
request.get(PAGES_URL);
In this client, you can also retrieve the token from the localStorage / cookie, as you want.
Similarly, we have a function to set or delete the token from calls like this:
import axios from 'axios';
export default function setAuthToken(token) {
axios.defaults.headers.common['Authorization'] = '';
delete axios.defaults.headers.common['Authorization'];
if (token) {
axios.defaults.headers.common['Authorization'] = `${token}`;
}
}
We always clean the existing token at initialization, then establish the received one.
The point is to set the token on the interceptors for each request
import axios from "axios";
const httpClient = axios.create({
baseURL: "http://youradress",
// baseURL: process.env.APP_API_BASE_URL,
});
httpClient.interceptors.request.use(function (config) {
const token = localStorage.getItem('token');
config.headers.Authorization = token ? `Bearer ${token}` : '';
return config;
});
If you want to call other api routes in the future and keep your token in the store then try using redux middleware.
The middleware could listen for the an api action and dispatch api requests through axios accordingly.
Here is a very basic example:
actions/api.js
export const CALL_API = 'CALL_API';
function onSuccess(payload) {
return {
type: 'SUCCESS',
payload
};
}
function onError(payload) {
return {
type: 'ERROR',
payload,
error: true
};
}
export function apiLogin(credentials) {
return {
onSuccess,
onError,
type: CALL_API,
params: { ...credentials },
method: 'post',
url: 'login'
};
}
middleware/api.js
import axios from 'axios';
import { CALL_API } from '../actions/api';
export default ({ getState, dispatch }) => next => async action => {
// Ignore anything that's not calling the api
if (action.type !== CALL_API) {
return next(action);
}
// Grab the token from state
const { token } = getState().session;
// Format the request and attach the token.
const { method, onSuccess, onError, params, url } = action;
const defaultOptions = {
headers: {
Authorization: token ? `Token ${token}` : '',
}
};
const options = {
...defaultOptions,
...params
};
try {
const response = await axios[method](url, options);
dispatch(onSuccess(response.data));
} catch (error) {
dispatch(onError(error.data));
}
return next(action);
};
Sometimes you get a case where some of the requests made with axios are pointed to endpoints that do not accept authorization headers. Thus, alternative way to set authorization header only on allowed domain is as in the example below. Place the following function in any file that gets executed each time React application runs such as in routes file.
export default () => {
axios.interceptors.request.use(function (requestConfig) {
if (requestConfig.url.indexOf(<ALLOWED_DOMAIN>) > -1) {
const token = localStorage.token;
requestConfig.headers['Authorization'] = `Bearer ${token}`;
}
return requestConfig;
}, function (error) {
return Promise.reject(error);
});
}
Try to make new instance like i did below
var common_axios = axios.create({
baseURL: 'https://sample.com'
});
// Set default headers to common_axios ( as Instance )
common_axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;
// Check your Header
console.log(common_axios.defaults.headers);
How to Use it
common_axios.get(url).......
common_axios.post(url).......
export const authHandler = (config) => {
const authRegex = /^\/apiregex/;
if (!authRegex.test(config.url)) {
return store.fetchToken().then((token) => {
Object.assign(config.headers.common, { Authorization: `Bearer ${token}` });
return Promise.resolve(config);
});
}
return Promise.resolve(config);
};
axios.interceptors.request.use(authHandler);
Ran into some gotchas when trying to implement something similar and based on these answers this is what I came up with. The problems I was experiencing were:
If using axios for the request to get a token in your store, you need to detect the path before adding the header. If you don't, it will try to add the header to that call as well and get into a circular path issue. The inverse of adding regex to detect the other calls would also work
If the store is returning a promise, you need to return the call to the store to resolve the promise in the authHandler function. Async/Await functionality would make this easier/more obvious
If the call for the auth token fails or is the call to get the token, you still want to resolve a promise with the config

React Admin how to use Basic Authentification ? In my sample, Auth headers are well sent to the entrypoint, but not to all others endpoints

i have an api that runs under /api
It requires Basic Http authentification (no needs of JWT thanks to this article https://jolicode.com/blog/why-you-dont-need-jwt).
So i configured my authProvider and my fetchHydra to build the Header.
This header is well sent to those 3 main endpoints:
/api
/api/docs.jsonld
/Entrypoint
But then, it try to call all resources endpoint without using Basic Http so they all response with HTTP 401.
Here is my code:
// admin.js (app main resource)
import React from 'react';
import parseHydraDocumentation from '#api-platform/api-doc-parser/lib/hydra/parseHydraDocumentation';
import { HydraAdmin, hydraClient, fetchHydra as baseFetchHydra } from '#api-platform/admin';
import ReactDOM from 'react-dom';
import authProvider from './src/authProvider';
import { Route, Redirect } from 'react-router-dom';
const entrypoint = document.getElementById('api-entrypoint').innerText;
// Fetch api route with Http Basic auth instead of JWT Bearer system
const fetchHeaders = {"Authorization": `Basic ${btoa(`${localStorage.getItem('username')}:${localStorage.getItem('token')}`)}`};
// original system with JWT
// const fetchHeaders = {'Authorization': `Bearer ${localStorage.getItem('token')}`};
const fetchHydra = (url, options = {}) => baseFetchHydra(url, {
...options,
headers: new Headers(fetchHeaders),
});
const dataProvider = api => {
return hydraClient(api, fetchHydra);
}
const apiDocumentationParser = entrypoint =>
parseHydraDocumentation(entrypoint, {
headers: new Headers(fetchHeaders),
}).then(
({ api }) => ({ api }),
result => {
const { api, status } = result;
if (status === 401) {
return Promise.resolve({
api,
status,
customRoutes: [
<Route path="/" render={() => <Redirect to="/login" />} />,
],
});
}
return Promise.reject(result);
}
);
ReactDOM.render(
<HydraAdmin
apiDocumentationParser={apiDocumentationParser}
authProvider={authProvider}
entrypoint={entrypoint}
dataProvider={dataProvider}
/>, document.getElementById('api-platform-admin'));
// admin/src/authProvider.js
import { AUTH_LOGIN, AUTH_LOGOUT, AUTH_ERROR, AUTH_CHECK } from 'react-admin';
// Change this to be your own authentication token URI.
const authenticationTokenUri = `${document.getElementById('api-entrypoint').innerText}/login`;
export default (type, params) => {
switch (type) {
case AUTH_LOGIN:
const { username, password } = params;
const request = new Request(authenticationTokenUri, {
method: 'POST',
body: JSON.stringify({ username: username, password }),
headers: new Headers({ 'Content-Type': 'application/json' }),
});
return fetch(request)
.then(response => {
if (response.status < 200 || response.status >= 300) throw new Error(response.statusText);
return response.json();
})
.then(({ token }) => {
localStorage.setItem('username', username);
localStorage.setItem('token', token); // The token is stored in the browser's local storage
window.location.replace('/');
});
case AUTH_LOGOUT:
localStorage.removeItem('username');
localStorage.removeItem('token');
break;
case AUTH_ERROR:
if (401 === params.status || 403 === params.status) {
localStorage.removeItem('username');
localStorage.removeItem('token');
return Promise.reject();
}
break;
case AUTH_CHECK:
return localStorage.getItem('token') ? Promise.resolve() : Promise.reject();
default:
return Promise.resolve();
}
}
My application is using PHP Symfony Api-Platform (2.4.5) and Api-Platform Admin (0.6.3 which embed the react admin ^2.7.0)
I pushed the repo on github: https://github.com/Rebolon/LibraryManagementSystem
Ok, so the problem is not related of my code. Oh yeah, it's not my fault.
In fact it's related to version 0.6.3 of package #api-platform/admin which breaks the authentication system.
The solution is to rollback to version 0.6.2 of the package.
Thanks to this thread: https://github.com/api-platform/admin/issues/185

What is the proper pattern to intercept requests and manage tokens and token renewals using axios.interceptors

I might need some help how to use axios interceptors here. This is a part of the login screen. I want to have the interceptor refresh the token when its missing or its expired. I am not even sure it is the right place to have this code as token refresh will needs to be happening after the login too.
I have two axios instances. One for the API and one for auth0. I would need a way to connect them so they work together and reliable.
import token from "../../network/axios-auth0";
import client from "../../network/axios-client";
//Gets token and updates the redux token value
getToken() {
token.post("token", {
client_id: config.clientId,
client_secret: config.clientSecret,
audience: config.clientAudience,
grant_type: "client_credentials"
})
.then(response => {
this.props.onUpdateToken(response.data.access_token);
this.getGroup(response.data.access_token)
})
.catch(function (error) {
console.log(error);
})
}
//Gets token from redux and gets data
getGroup() {
client.interceptors.request.use(function (config) {
//call getToken() for new token or check old token
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
client.get("groups/5ca21cd1cb9134222e397f14", {data: {}, headers: {"Authorization":"Bearer " + this.props.bearerToken}})
.then(response => {
console.log(response);
})
.catch(function (error) {
console.log(error);
})
}
onLoginPress(){
this.getToken();
}
render() {
return (
<View>
<TextInput placeholder={"user"}/>
<TextInput placeholder={"pass"}/>
<Button title={"login"} onPress={() => this.onLoginPress()}/>
</View>
)
}
const mapStateToProps = state => {
return {
token: state.values.token
};
};
const mapDispatchToProps = dispatch => {
return {
onUpdateToken: (value: string) => dispatch(updateToken(value))
};
};
export default connect(mapStateToProps, mapDispatchToProps)(LoginScreen);
I mean the interceptor will be needed in every axios call (to prevent a request getting stuck because of old token) So it would be best to attach it to the instance. But then I don't know how to update the token in redux from the file where the instance is defined. It's probably not the best practice either. Here is one of the 2 instances defined.
import axios from "axios"
import config from "../config"
const client = axios.create({
baseURL: config.apiUrl,
headers: {
"Content-type": "application/json"
}
});
client.defaults.headers.get["Content-Type"] = "application/json";
export default client;
How would one do this professionally. I don't want to copy the interceptor for each call in the app.

Categories