How to pass accessToken to Axios interceptor without Redux - reactjs

I am trying to authorize a Next app using the existing Nodejs backend using a manual JWT strategy.
My backend issues an access token, and I'm trying to sign each request with accessToken using axios.interceptor where I set Bearer ${token}
// utils/axios.ts
const axiosPrivate = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
timeout: 1000,
withCredentials: true,
headers: {
'Content-Type': 'application/json',
},
});
axiosPrivate.interceptors.request.use(
(config: AxiosRequestConfig): AxiosRequestConfig => {
if (config.headers === undefined) {
config.headers = {};
}
// no way to access sessionStorage or document.cookies
config.headers['Authorization'] = `Bearer ${accessToken}`
return config;
}
);
export { axiosPrivate }
My question is if there is some way to grab access token without Redux?
I want it in the interceptor because it will let me do SSR like that:
// pages/dashboard.tsx
export const getServerSideProps: GetServerSideProps = async (context) => {
const res = await axiosPrivate.get('/dashboard'); // request already signed
return {
props: {
dashboard: res.data,
},
};
};

You can pass the accessToken in the params for the axios request.
<code>await axiosPrivate.get('/dashboard', {
params: {
accessToken
}
});
</code>

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

Application does not fetch access token from cache using MSAL (react-aad-msal)

authProvider.getAccessToken() calls the authentication endpoint for every API call, instead of fetching it from the cache.
I don't know if the issue is with AcquireTokenSilent in Msal or getAccessToken in react-aad-msal.
Using msal 1.2.1 and react-aad-msal 2.3.2
Api call helper:
import { config } from '../config';
import { authProvider } from './../authProvider';
export const callApi = async (method: 'GET' | 'POST' | 'PUT' | 'DELETE', path: string, data?: any) => {
const token = await authProvider.getAccessToken();
const res = await fetch(`${config.API_ENDPOINT}/api/${path}`, {
method,
headers: {
Authorization: 'Bearer ' + token.accessToken,
'Content-Type': 'application/json',
},
body: JSON.stringify(data),
});
return res.json();
};
Config:
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
// Msal Configurations
const config = {
auth: {
authority: 'https://login.microsoftonline.com/<my tenant id>',
clientId: '<my client id>',
},
cache: {
cacheLocation: 'localStorage',
storeAuthStateInCookie: false,
},
};
// Authentication Parameters
const authenticationParameters = {
scopes: ['offline_access'],
};
// Options
const options = {
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin + '/auth.html',
};
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options);
I stumbled to the same issue, try to add the required scope to the access token request.
const token = await authProvider.getAccessToken({
scopes: ['offline_access']
});
I fixed the problem by removing 'offline_access' from scopes, as it seems it's added implicitially, and adding it manually causes MSAL to not find the cached token as the scopes are used as key.
I had to add my custom scopes as well, in my case 'User.Read'
const authenticationParameters = {
scopes: ['User.Read'],
};
const tokenResponse = await MsalInstance.acquireTokenSilent({
scopes: ['User.read', 'whatyouwant','whatyouneed','whatyoudeepdesire'],
account: MsalInstance.getAccountByUsername(username) , // or MsalInstance.getAllAccounts()[0].username
forceRefresh: true
}).then(data=>{
if (data.idTokenClaims) {
console.log(data.idTokenClaims.exp)
}
})

Handling firebase initialization delay and id tokens for custom apollo graphql backend

Currently, when I authenticate a user with firebase, I store their auth token in localStorage to be used later to connect to my backend like so:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const authMiddleware = new ApolloLink((operation, forward) => {
// add the authorization token to the headers
const token = localStorage.getItem(AUTH_TOKEN) || null
operation.setContext({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
return forward(operation)
})
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: authMiddleware.concat(authAfterware).concat(httpLink)
})
}
My problem with this is that I have no way to refresh the token once it expires. So I tried to use the following to set the authorization token for apollo:
const httpLink = new HttpLink({uri: 'http://localhost:9000/graphql'})
const asyncAuthLink = setContext(
() => {
return new Promise((success, reject) => {
firebase.auth().currentUser.getToken().then(token => {
success({
headers: {
authorization: token ? `Bearer ${token}` : ''
}
})
}).catch(error => {
reject(error)
})
})
}
)
const authAfterware = onError(({networkError}) => {
if (networkError.statusCode === 401) AuthService.signout()
})
function createApolloClient() {
return new ApolloClient({
cache: new InMemoryCache(),
link: asyncAuthLink.concat(authAfterware.concat(httpLink))
})
}
This works when the user first authenticates, but once the user refreshes the page, firebase is no longer initialized when my graphql queries are sent to my backend, so the token is not sent with it. Is there a way I can asynchronously wait for firebase.auth().currentUser so this will work? Or is there another approach I should take entirely? As far as I know (100% sure) currentUser.getIdToken only makes a network call if the current token is no longer valid. I think this is acceptable as in cases where the token is not valid, the backend can't respond anyway, so I will need to wait for a token refresh to continue.
Some other ideas I thought of:
Continue to use localStorage to store the auth token, refresh it in authAfterware if my backend sends a 401 response back and retry the request.
Set a delay on getting the auth token (not desirable)
Any other ideas?
Thanks!
I know is a bit late but I was stuck on that as well and found a way to solve it. Maybe is not the best one but at least it works.
My approach is to create a Next api endpoint to retrieve the user token using the getUserFromCookies method:
import { NextApiRequest, NextApiResponse } from "next";
import { getUserFromCookies } from "next-firebase-auth";
import initAuth from "../../utils/initAuth";
initAuth();
const handler = async (req: NextApiRequest, res: NextApiResponse<any>) => {
try {
const user = await getUserFromCookies({ req, includeToken: true });
const accessToken = await user.getIdToken();
return res.status(200).json({ success: true, accessToken });
} catch (e) {
console.log(`${e}`);
return res.status(500).json({ error: `Unexpected error. ${e}` });
}
};
export default handler;
And then call this endpoint in the apollo client config like that:
import { ApolloClient, InMemoryCache, ApolloLink, HttpLink, concat } from "#apollo/client";
import { setContext } from "#apollo/client/link/context";
import { relayStylePagination } from "#apollo/client/utilities";
const getUserToken = async () => {
const res = await fetch("http://localhost:3000/api/get-user-token");
const { accessToken } = await res.json();
return accessToken;
};
const asyncAuthLink = setContext(async (request) => {
const token = await getUserToken();
return { ...request, headers: { authorization: token ? `Bearer ${token}` : "" } };
});
const httpLink = new HttpLink({ uri: process.env.NEXT_PUBLIC_API_URL });
const client = new ApolloClient({
name: "web",
version: "1.0",
uri: process.env.NEXT_PUBLIC_API_URL,
cache: new InMemoryCache({
typePolicies: {
Query: {
fields: {
users: relayStylePagination(),
},
},
},
}),
link: concat(asyncAuthLink, httpLink),
});
export default client;

LocalStorage not working after storing token

I am storing JWT token in LocalStorage after Login success is dispatched and routing to next component. But the next component API call is not able to take LocalStored token.
If i refresh the page and click again, it is accepting the token. Dont know the issue.
This is Axios Instance and Login Dispatch respectively
const instance = axios.create({
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8',
'x-access-token': localStorage.getItem('accessToken'),
},
withCredentials: true,
validateStatus: (status) => status === 200
});
export function checkLogin(data, history) {
return function (dispatch) {
return dispatch(makeAPIRequest(loginAPI, data)).then(function (response) {
if (response.data.success == 1) {
localStorage.removeItem('accessToken')
localStorage.setItem('accessToken', response.data.data.token)
dispatch({ type: STORE_SESSION_TOKEN, authenticated: response.data.data.auth, token: response.data.data.token,userDetails: response.data.data.user });
history.push('/dashboard')
}
})
}
}
Expecting to take token from Localstorage from very next call from Dashboard. But that doesn't happen. says no token and redirects to Login
I think the problem is when you create the axios instance, the token is not available in localStorage.
You should try the default headers of axios:
export const AUTHORIZATION = 'authorization'
export const API = axios.create({
baseURL: `http://localhost:3000/api`
})
export const authorize = (token) => {
if (token) {
API.defaults.headers.Authorization = `Bearer ${token}`
localStorage.setItem(AUTHORIZATION, token)
}
}
export const unauthorize = () => {
delete API.defaults.headers.Authorization
localStorage.removeItem(AUTHORIZATION)
}
authorize(localStorage.getItem(AUTHORIZATION))
When you receive the token from the API in your actions, you have to save it to localStorage.
You need to get your header dynamically, because the token is not there while creating the instance and it might change later:
const JSON_HEADERS = {
'Accept': 'application/json',
'Content-Type': 'application/json; charset=utf-8'
};
const getTokenFromStorage = () => localStorage.getItem('token');
const getHeaders = () => getTokenFromStorage().then((token) => {
if (!token) {
return JSON_HEADERS;
}
return {
... JSON_HEADERS,
'x-access-token': token
};
});
export const makeAPIRequest = ({ method, url, data }) => {
return axios({
method,
url,
data,
headers: getHeaders()
});
}

Sending the bearer token with axios

In my react app i am using axios to perform the REST api requests.
But it's unable to send the Authorization header with the request.
Here is my code:
tokenPayload() {
let config = {
headers: {
'Authorization': 'Bearer ' + validToken()
}
}
Axios.post(
'http://localhost:8000/api/v1/get_token_payloads',
config
)
.then( ( response ) => {
console.log( response )
} )
.catch()
}
Here the validToken() method would simply return the token from browser storage.
All requests are having a 500 error response saying that
The token could not be parsed from the request
from the back-end.
How to send the authorization header with each requests? Would you recommend any other module with react?
const config = {
headers: { Authorization: `Bearer ${token}` }
};
const bodyParameters = {
key: "value"
};
Axios.post(
'http://localhost:8000/api/v1/get_token_payloads',
bodyParameters,
config
).then(console.log).catch(console.log);
The first parameter is the URL.
The second is the JSON body that will be sent along your request.
The third parameter are the headers (among other things). Which is JSON as well.
Here is a unique way of setting Authorization token in axios. Setting configuration to every axios call is not a good idea and you can change the default Authorization token by:
import axios from 'axios';
axios.defaults.baseURL = 'http://localhost:1010/'
axios.defaults.headers.common = {'Authorization': `bearer ${token}`}
export default axios;
Some API require bearer to be written as Bearer, so you can do:
axios.defaults.headers.common = {'Authorization': `Bearer ${token}`}
Now you don't need to set configuration to every API call. Now Authorization token is set to every axios call.
You can create config once and use it everywhere.
const instance = axios.create({
baseURL: 'https://example.com/api/',
timeout: 1000,
headers: {'Authorization': 'Bearer '+token}
});
instance.get('/path')
.then(response => {
return response.data;
})
The second parameter of axios.post is data (not config). config is the third parameter. Please see this for details: https://github.com/mzabriskie/axios#axiosposturl-data-config
By using Axios interceptor:
const service = axios.create({
timeout: 20000 // request timeout
});
// request interceptor
service.interceptors.request.use(
config => {
// Do something before request is sent
config.headers["Authorization"] = "bearer " + getToken();
return config;
},
error => {
Promise.reject(error);
}
);
If you want to some data after passing token in header so that try this code
const api = 'your api';
const token = JSON.parse(sessionStorage.getItem('data'));
const token = user.data.id; /*take only token and save in token variable*/
axios.get(api , { headers: {"Authorization" : `Bearer ${token}`} })
.then(res => {
console.log(res.data);
.catch((error) => {
console.log(error)
});
Just in case someone faced the same issue.
The issue here is when passing the header without data, the header's configuration will be in the payload data, So I needed to pass null instead of data then set the header's configuration.
const config = {
headers: {
"Content-type": "application/json",
"Authorization": `Bearer ${Cookies.get("jwt")}`,
},
};
axios.get(`${BASE_URL}`, null, config)
This works and I need to set the token only once in my app.js:
axios.defaults.headers.common = {
'Authorization': 'Bearer ' + token
};
Then I can make requests in my components without setting the header again.
"axios": "^0.19.0",
I use a separate file to init axios instance and at the same time, I add intercepters to it. Then in each call, the intercepter will add the token to the request header for me.
import axios from 'axios';
import { getToken } from '../hooks/useToken';
const axiosInstance = axios.create({
baseURL: process.env.REACT_APP_BASE_URL,
});
axiosInstance.interceptors.request.use(
(config) => {
const token = getToken();
const auth = token ? `Bearer ${token}` : '';
config.headers.common['Authorization'] = auth;
return config;
},
(error) => Promise.reject(error),
);
export default axiosInstance;
Here is how I use it in the service file.
import { CancelToken } from 'axios';
import { ToolResponse } from '../types/Tool';
import axiosInstance from './axios';
export const getTools = (cancelToken: CancelToken): Promise<ToolResponse> => {
return axiosInstance.get('tool', { cancelToken });
};
// usetoken is hook i mad it
export const useToken = () => {
return JSON.parse(localStorage.getItem('user')).token || ''
}
const token = useToken();
const axiosIntance = axios.create({
baseURL: api,
headers: {
'Authorization':`Bearer ${token}`
}
});
axiosIntance.interceptors.request.use((req) => {
if(token){
req.headers.Authorization = `Bearer ${token}`;
}
return req;
})
If you are sending a post request with empty data remember to always set the second parameter to either empty object or empty string just as in the example below. e.g: axios.post('your-end-point-url-here', '', config)
if you don't set it axios will assume that whatever you are passing as the second parameter is a formData
const config = {
headers: { Authorization: `Bearer ${storage.getToken()}` }
};
axios
.post('http://localhost:8000/api/v1/get_token_payloads', {}, config)
.then(({ data: isData }) => {
console.log(isData);
})
.catch(error => {
console.log(error);
});
You must mention the 2nd parameter body for the post request even if it is empty, try this :
tokenPayload() {
let config = {
headers: {
'Authorization': 'Bearer ' + validToken()
}
}
Axios.post(
'http://localhost:8000/api/v1/get_token_payloads',
// empty body
{},
config
)
.then( (response) => {
console.log(response)
} )
.catch()
}
You can try configuring the header like this:
const headers = {"Content-Type": "text/plain", "x-access-token": token}
You can use interceptors in axios:
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
More on that you can find here: https://axios-http.com/docs/interceptors
there are a lot of good solution but I use this
let token=localStorage.getItem("token");
var myAxios=axios.create({
baseURL: 'https://localhost:5001',
timeout: 700,
headers: {'Authorization': `bearer ${token}`}
});
export default myAxios;
then i import myaxios to my file and
myAxios.get("sth")
axios by itself comes with two useful "methods" the interceptors that are none but middlewares between the request and the response. so if on each request you want to send the token. Use the interceptor.request.
I made apackage that helps you out:
$ npm i axios-es6-class
Now you can use axios as class
export class UserApi extends Api {
constructor (config) {
super(config);
// this middleware is been called right before the http request is made.
this.interceptors.request.use(param => {
return {
...param,
defaults: {
headers: {
...param.headers,
"Authorization": `Bearer ${this.getToken()}`
},
}
}
});
this.login = this.login.bind(this);
this.getSome = this.getSome.bind(this);
}
login (credentials) {
return this.post("/end-point", {...credentials})
.then(response => this.setToken(response.data))
.catch(this.error);
}
getSome () {
return this.get("/end-point")
.then(this.success)
.catch(this.error);
}
}
I mean the implementation of the middleware depends on you, or if you prefer to create your own axios-es6-class
https://medium.com/#enetoOlveda/how-to-use-axios-typescript-like-a-pro-7c882f71e34a
it is the medium post where it came from

Resources