React - Login success redirect and auth headers - reactjs

I have a login function that should set the user's credentials in sessionStorage upon successful login and then redirect to a new route, calling another api with the sessionStorage auth token added to the headers. For some reason the initial api call after successful login is failing because the auth token isn't added to the request headers. However, if I reload the page after the redirect the request header is added, resulting in a successful response. What is the correct way to save credentials to sessionStorage and configure all headers for subsequent requests.
components/Login.jsx
login(event) {
event.preventDefault();
this.props.dispatch(loginUser(this.state.creds));
}
actions/loginActions.js
export function loginUser(user) {
return function(dispatch) {
return LoginApi.login(user).then(creds => {
dispatch(loginUserSuccess(creds));
}).catch(error => {
throw(error);
});
};
}
export function loginUserSuccess(creds) {
sessionStorage.setItem('credentials', JSON.stringify(creds.data));
hashHistory.push('/packages');
return {
type: types.LOGIN_USER_SUCCESS,
state: creds.data
}
}
api/config.js
import axios from 'axios';
sessionStorage.credentials ? axios.defaults.headers.common['Authorization'] = 'Bearer ' + JSON.parse(sessionStorage.credentials).authToken : undefined;
api/packageApi.js
import './config';
import axios from 'axios';
class PackageApi {
static getAllPackages() {
return axios.get('/get/my/packages')
.then(function (response) {
console.log(response);
return response;
})
.catch(function (error) {
console.log(error);
});
}
}
Edit
// same behavior
sessionStorage.getItem('credentials') ? axios.defaults.headers.common['Authorization'] = 'Bearer ' + JSON.parse(sessionStorage.credentials).authToken : undefined;
// Fixed - api/config.js
import axios from 'axios';
const axiosInstance = axios.create();
axiosInstance.interceptors.request.use(
config => {
sessionStorage.getItem('credentials') ? config.headers['Authorization'] = 'Bearer ' + JSON.parse(sessionStorage.credentials).authToken : undefined;
return config;
},
error => Promise.reject(error)
);
export default axiosInstance;
// Fixed - api/packageApi.js
import axiosInstance from './config';
class PackageApi {
static getAllPackages() {
return axiosInstance.get('/get/my/packages')
.then(function (response) {
console.log(response);
return response;
})
.catch(function (error) {
console.log(error);
});
}
}
export default PackageApi;

Your config file is parsed and evaluated as soon as the browser loads it. At that time there is no token in your sessionStorage. What you need to do is to write a function which is only defined at parse time but then you call it at runtime to get the token:
class PackageApi {
static getAllPackages() {
const token = window.sessionStorage.getItem('credentials')
return axios.get('/get/my/packages', {
headers: {'Authorization': `Bearer ${ token }`}
})
.then(function (response) {
console.log(response);
return response;
})
.catch(function (error) {
console.log(error);
});
}
}
Here I did not define a function because sessionStorage.getItem is already a function but it doesn't stop you to write stuff like this:
const getFromStorage = key => {
const value = window.sessionStorage.getItem(key);
return JSON.parse(value);
}
axios.get('/get/my/packages', {
headers: {'Authorization': `Bearer ${ getFromStorage('credentials') }`}
})

Related

ReactJs how to add interceptor in axios

I've been working on this for hours, and I have no idea where did it go wrong.
I want to have an axios interceptor for my ReactJs
this is my interceptor axiosHandler.js
import axios from "axios";
const axiosHandler = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
headers: {
Accept: "application/json",
},
});
axiosHandler.interceptors.request.use(
(config) => {
const token = localStorage.getItem("token");
if (token) {
config.headers["Authorization"] = "Bearer " + token;
}
return config;
},
(error) => {
Promise.reject(error);
}
);
//axiosHandler.interceptors.response
export default axiosHandler;
And here is how I use the handler in my other component
import axiosHandler from "../services/axiosHandler";
const getData = async () => {
await axiosHandler
.get(`/path`)
.then((response) => {
//do something
})
};
And I get an error of below
services_axiosHandler__WEBPACK_IMPORTED_MODULE_0_.get is not a function
I've read many other solutions, but I can't find the difference as how it leads to the error of mine.
Where do I put it wrong?
Thank you
inside axios.index
import axios from "axios";
import { API_URL } from "../config/config";
const axiosHttp = axios.create({
baseURL: `${API_URL}`,
});
axiosHttp.interceptors.request.use(
(config) => {
const token = "Your Token here"
return {
...config,
headers: {
...(token !== null && { Authorization: `${token}` }),
...config.headers,
},
};
},
(error) => {
return Promise.reject(error);
}
);
axiosHttp.interceptors.response.use(
(response) => {
//const url = response.config.url;
//setLocalStorageToken(token);
return response;
},
(error) => {
if (error.response.status === 401) {
//(`unauthorized :)`);
//localStorage.removeItem("persist:root");
//removeLocalStorageToken
//window.location.href = "/login";
}
return Promise.reject(error);
}
);
export default axiosHttp;
Then inside your API function use it like below
import axiosHttp from "./utils/axios";
const getData = async ()=>{
try{
const response = await axiosHttp.get('/path')
return resposne;
}
catch(error){
//handle error here...
}
}
Last but not least, you shouldn't use await when using callback (then/catch)

Axios Interceptor is not working in React JS

I am using the below code as an interceptor in my React JS app for getting token back but unfortunately, it is not working. Refresh token returns new idToken and updates local storage data correctly. The same code I'm using some other application which works fine. One main difference is that I currently use React 18 and the previous 16. I struggled to identify the problem but failed. Your help will be appreciable.
axios.interceptors.response.use(
(response) => {
return response;
},
(error) => {
if (error.response.status === 401) {
// console.log(error.response.data.code)
let usersData = JSON.parse(localStorage.getItem("userData"));
const refreshToken = usersData.refreshToken;
return axios
.post(
`${api_base_url}/auth/authentication/refresh_token`,
JSON.stringify({
refresh_token: refreshToken,
})
)
.then((response) => {
usersData["accessToken"] = response.data.data.accessToken;
usersData["idToken"] = response.data.data.idToken;
setSessionStorage("userData", usersData);
error.response.config.headers[
"Authorization"
] = `Bearer ${response.data.data.idToken}`;
return axios(error.response.config);
})
.catch((error) => {
if (error.response.data.code !== "TOKEN_EXPIRED") {
return;
}
localStorage.clear();
window.location = "/login";
});
}
return Promise.reject(error);
}
);
function getIRequestProp(severType, isMultipart, isSocial) {
const serverUrl = severType ? social_api_base_url : api_base_url;
let userData = JSON.parse(localStorage.getItem('userData'));
let idToken;
idToken = userData !== null ? userData['idToken'] : '';
let content_type;
if (isSocial) {
content_type = 'application/x-www-form-urlencoded'
} else {
content_type = isMultipart ? 'multipart/form-data' : 'application/json'
}
return {
serverUrl: serverUrl,
requestHeader: {
'Content-Type': content_type,
'Accept-Language': DEFAULT_LANGUAGE,
Authorization: `Bearer ${idToken}`
}
};
}
async function post(url, body, isSocialServer, isMultipart) {
const {serverUrl, requestHeader} = getIRequestProp(isSocialServer, isMultipart);
return axios.post(serverUrl + url, body, {
headers: requestHeader
});
}
So, I call API like this:
AxiosServices.post(ApiUrlServices.SOCIALS_UPDATE_LINKS(UserInfo.userId), payload, false)
.then(response => {})
What i figured out that return axios(error.response.config); is not sending authorization token in API request headers and trying request infinitely. But consoling error.response.config shows token sets in the config correctly.
Adding an additional modification of axios request, I solved my problem.
axios.interceptors.request.use(request => {
// Edit request config
let usersData = JSON.parse(localStorage.getItem('userData'));
request.headers['Authorization'] = `${usersData.idToken}`;
return request;
}, error => {
return Promise.reject(error);
});

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

React - fetch-intercept modify all headers

What is the proper way to intercept all requests and add headers using react with fetch-intercept? I have a config file that contains the call to fetchIntercept.register(). I have separate files for component api calls that import the fetchIntercept config file. I added the unregister() call after the api is called but the headers are not being added.
api/config.js
import fetchIntercept from 'fetch-intercept';
const unregister = fetchIntercept.register({
request: function (url, config) {
// Modify the url or config here
const withDefaults = Object.assign({}, config);
withDefaults.headers = defaults.headers || new Headers({
'AUTHORIZATION': `Bearer ${JSON.parse(sessionStorage.credentials).authToken}`
});
return [url, withDefaults];
},
requestError: function (error) {
// Called when an error occured during another 'request' interceptor call
return Promise.reject(error);
},
response: function (response) {
// Modify the reponse object
return response;
},
responseError: function (error) {
// Handle an fetch error
return Promise.reject(error);
}
});
export default unregister;
api/packageApi.js
import unregister from '../api/config';
class PackageApi {
static getAllPackages() {
const request = new Request('/get/my/packages', {
method: 'GET'
});
return fetch(request).then(response => {
return response.json();
}).catch(error => {
return error;
});
}
}
unregister();
export default PackageApi;
I adding the working example for the fetch-intercept in separate file, its works for me perfectly.
https://stackblitz.com/edit/react-fetch-intercept-bi55pf?file=src/App.js
App.js
import React from 'react';
import './style.css';
import { AuthInterceptor } from './AuthInterceptor';
export default class App extends React.Component {
componentDidMount() {
AuthInterceptor();
fetch('http://google.com', {
headers: {
'Content-type': 'application/json',
},
});
}
render() {
return (
<div>
<h1>Hello StackBlitz!</h1>
<p>Start editing to see some magic happen :)</p>
</div>
);
}
}
AuthInterceptor.js
import fetchIntercept from 'fetch-intercept';
export const AuthInterceptor = () => {
fetchIntercept.register({
request: function (url, config) {
// Modify the url or config here
config.headers.name = 'Aravindh';
console.log(config);
return [url, config];
},
requestError: function (error) {
// Called when an error occured during another 'request' interceptor call
return Promise.reject(error);
},
response: function (response) {
// Modify the reponse object
return response;
},
responseError: function (error) {
// Handle an fetch error
return Promise.reject(error);
},
});
};
You can see the updated header value in the console.
Thanks
The use of unregister seems incorrect. You have unregistered before any calls are made.
This is pretty straight forward using axios instead of fetch.

Resources