Where to check for token expiration and send refresh request - react-admin

I have set up react-admin and am using it with the HydraAdmin component.
My login request returns a JWT and a refresh token. I store these in localStorage and want to check whether the JWT is expired before sending requests.
The problem is, where do I do that?
I tried the checkAuth function of my auth provider, but it isn't called before every fetch (e.g. POST).
I want to reliably be able to refresh my token before it's sent with a request.
Or is this not the right way to deal with this?

I solved it by adding my refreshToken function call to each data provider function.
Not sure if this is the right way to do this, but it worked.
My function to refresh the token:
function setAuth(auth) {
localStorage.setItem('token', auth.token);
localStorage.setItem('refreshToken', auth.refreshToken);
authHeaders.set("Authorization", `Bearer ${auth.token}`);
}
function getRefreshToken() {
return localStorage.getItem('refreshToken');
}
function getToken() {
return localStorage.getItem('token');
}
function isLoggedIn() {
return getToken() && getRefreshToken();
}
function isTokenExpired() {
const decoded = jwt_decode(getToken());
return decoded.exp < Date.now() / 1000 + 60 * 5;
}
function postRequest(route, body) {
return fetch(new Request(url(config.server, route), {
method: 'POST',
body: JSON.stringify(body),
headers: new Headers({ 'Content-Type': 'application/json' }),
}));
}
export async function refreshToken() {
if (!isLoggedIn()) {
return;
}
if (isTokenExpired()) {
const response = await postRequest(config.auth.refresh_route, {refreshToken: getRefreshToken()});
if (response.ok) {
setAuth(await response.json());
}
}
}
My data provider:
export const authHeaders = new Headers();
const fetch = (url, options = {}) => fetchHydra(url, {...options, headers: authHeaders });
const provider = hydraDataProvider(config.api, fetch);
class DataProvider {
async introspect() {
await refreshToken();
return provider.introspect();
}
async getList (resource, params) {
await refreshToken();
return provider.getList(resource, params);
}
async getOne (resource, params) {
await refreshToken();
return provider.getOne(resource, params);
}
async getMany (resource, params) {
await refreshToken();
return provider.getMany(resource, params);
}
async getManyReference (resource, params) {
await refreshToken();
return provider.getManyReference(resource, params);
}
async create (resource, params) {
await refreshToken();
return provider.create(resource, params);
}
async update (resource, params) {
await refreshToken();
return provider.update(resource, params);
}
async updateMany (resource, params) {
await refreshToken();
return provider.updateMany(resource, params);
}
async delete (resource, params) {
await refreshToken();
return provider.delete(resource, params);
}
async deleteMany (resource, params) {
await refreshToken();
return provider.deleteMany(resource, params);
}
}
export const dataProvider = new DataProvider();

Related

In reactnative expo I tried using secureStore from expo in redux to save token the one I get from api

I tried using redux to save token the one I get from api in react native ..its working now.
First one is for settoken and other one is for gettoken.
enter image description here
export const verifyOTP = (formValues, actions) => {
return async (dispatch) => {
dispatch(startSubmitting());
const url = `/validate-otp`;
var formdata = new FormData();
formdata.append("mobile", formValues.mobile);
formdata.append("otp", formValues.otp);
const response = await api.post(url, formdata);
dispatch({
type: "VERIFY_OTP",
payload: response,
});
dispatch(stopSubmitting());
await SecureStore.setItemAsync("userToken", response.data.access_token);
};
};
export const checkUser = () => {
return async (dispatch) => {
const token = await SecureStore.getItemAsync("userToken");
const url = `/me`;
const response = await api
.post(url, { token })
.then((res) => {
return res;
})
.catch((error) => {
return error.response;
});
dispatch({
type: "CHECK_USER",
payload: response,
});
};
};
The Problem
you are mixing two different implementations in checkUser to handle a promise which is clearly incorrect and leads to the issues.
The Solution
since your other parts of codes use the async/await so try to remove then/catch block from the response constant:
const checkUser = () => {
return async (dispatch) => {
const url = '/me';
try {
const token = await SecureStore.getItemAsycn("userToken);
const response = await api.post(url, {token})
dispatch({type: "CHECK_USER", payload: response})
} catch (error) {
// to proper action on failure case
}
}
}
Note 1: always use async/await in try/catch block. more on MDN documentation.
Optional
since you are trying to call two async actions (once for getting token and once for calling '/me' API), I encourage you to use two different try/catch blocks to handle the failure case for each async action separately. for example:
const checkUser = () => {
return async (dispatch) => {
let token = null;
try {
token = await SecureStore.getItemAsync("userToken");
} catch (err) {
// proper action in case of failure on getting the token from storage
}
// you may need to ignore API calls without the token, so:
try {
if(token){
const url = '/me';
const response = await api.post(url, {token});
dispatch({type: "CHECK_USER", payload: response});
}
} catch (err) {
// take proper action with the error response according to your applicaiton
}
}
}

How to passing result of http request inside async in ExpressJS?

I have below code
async send(user, data) {
const postData = {
'data': 'john',
'secret': 'secret'
};
const dataJson = JSON.stringify(postData);
const options = {
hostname: 'example.com',
path: '/send',
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Content-Length': dataJson.length
}
};
const req = https.request(options, (res) => {
let data = '';
console.log('Status Code:', res.statusCode);
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Body: ', JSON.parse(data));
});
}).on("error", (err) => {
console.log("Error: ", err.message);
});
req.write(dataJson);
req.end();
//---------------
let postResult = // HERE I WANT TO GET WHAT HTTP POST REQUESTED (e.g dataJson.body?)
//---------------
let result;
try {
result = await this.users.collection('users').updateOne(
{
_id: user
},
{
$set: {
// I WANT TO USE THAT HERE
data1 : postResult,
data2 : data2
}
},
{ maxTimeMS: consts.DB_MAX_TIME_USERS }
);
} catch (err) {
log.error('DB', 'UPDATEFAIL id=%s error=%s', user, err.message);
err.message = 'Database Error, failed to update user';
err.code = 'InternalDatabaseError';
throw err;
}
return { success: true };
}
How to get those data to outside variable?
I almost crazy about this, been searching on google and not found anything
I am using express and native-http to make http request, are there any native-curl maybe?
thank you very much for all the help
Your current code is using callback to retrieve result, so you can initiate data variable to outside callback function
let data = '';
const req = https.request(options, (res) => {
console.log('Status Code:', res.statusCode);
res.on('data', (chunk) => {
data += chunk;
});
res.on('end', () => {
console.log('Body: ', JSON.parse(data));
});
})
And also there are other easier way to make http request with nodejs. you can check axios that support Promise and async/await.
you can use syntax like this with axios
const response = await axios.get('/user?ID=12345');
way more easier.

How to set authorization header coorectly?

Problem:
In my react native app in order to remove repeated calls I have developed a general POST GET methods in httpClient file. It code is look likes this.
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
axios.defaults.headers.post['Content-Type'] = 'application/json';
var instance = null;
const setAuthorisationHeder = async () => {
const token = JSON.parse(await AsyncStorage.getItem('auth_data'));
if (token) {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: 'Bearer' + token.accessToken,
});
} else {
console.log('>>>>>> instance', instance);
Object.assign(instance.headers, {
Authorization: '',
});
}
};
export const setHeader = () => {
console.log('>>>>>>>> HIIII');
instance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
instance.interceptors.response.use(
function (response) {
return response;
},
async function (error) {
if (error.response.status) {
if (error.response.status === 401) {
AsyncStorage.removeItem('auth_data');
} else {
throw error;
}
} else {
console.log(error);
}
},
);
};
export const Get = (route, data) => {
function getData() {
return instance.get(
route,
data == null ? {data: {}} : {data: JSON.stringify(data)},
);
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
return getData();
}
return setHeader().then(getData);
};
export const Post = (route, data) => {
console.log('>>>>>> route', route);
function postData() {
return instance.post(route, JSON.stringify(data));
}
if (instance) {
console.log('>>>>>> HIIIIii');
// setAuthorisationHeder();
// setAuthorisationHeder();
return postData();
}
return setHeader().then(postData);
};
Can some tell me a way to add an authorization header to this instance? My token is storing the Asyncstorage in the middle of some actions so at the beginning called I don't have the token. As my code setHeader is running only one time so I created a method call setAuthorisationHeder() function. But it is giving me can not find property .then error when I am putting a request. Can someone help me to solve this issue? Thank you?
you can define global headers once and use it in every network call.
https://github.com/axios/axios#global-axios-defaults
Create a global auth variable where you'll store the auth data from storage. Before making a request get the auth data and use interceptor to set the bearer token.
let authToken = '';
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
Interceptor
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
complete code
import axios from 'axios';
import AsyncStorage from '#react-native-community/async-storage';
let authToken = '';
const axiosInstance = axios.create({
baseURL: '',
timeout: 150000,
headers: {
'Content-Type': 'application/json',
},
});
// request interceptor
axiosInstance.interceptors.request.use(
function (config) {
// Do something before request is sent
config.headers.Authorization = `Bearer ${authToken}`;
return config;
},
function (error) {
// Do something with request error
return Promise.reject(error);
}
);
const getAuthToken = async () => {
// asumming auth token was saved as string
authToken = await AsyncStorage.getItem('auth_data');
};
export const Get = async (route, data = {}) => {
// get and set auth token
await getAuthToken();
// route = /user?id=787878 or /user/787878
return await axiosInstance.get(route);
};
export const Post = async (route, data = {}) => {
await getAuthToken();
return await axiosInstance.post(route, data);
};

How to refresh JWT token using Apollo and GraphQL

So we're creating a React-Native app using Apollo and GraphQL. I'm using JWT based authentication(when user logs in both an activeToken and refreshToken is created), and want to implement a flow where the token gets refreshed automatically when the server notices it's been expired.
The Apollo Docs for Apollo-Link-Error provides a good starting point to catch the error from the ApolloClient:
onError(({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
case 'UNAUTHENTICATED':
// error code is set to UNAUTHENTICATED
// when AuthenticationError thrown in resolver
// modify the operation context with a new token
const oldHeaders = operation.getContext().headers;
operation.setContext({
headers: {
...oldHeaders,
authorization: getNewToken(),
},
});
// retry the request, returning the new observable
return forward(operation);
}
}
}
})
However, I am really struggling to figure out how to implement getNewToken().
My GraphQL endpoint has the resolver to create new tokens, but I can't call it from Apollo-Link-Error right?
So how do you refresh the token if the Token is created in the GraphQL endpoint that your Apollo Client will connect to?
The example given in the the Apollo Error Link documentation is a good starting point but assumes that the getNewToken() operation is synchronous.
In your case, you have to hit your GraphQL endpoint to retrieve a new access token. This is an asynchronous operation and you have to use the fromPromise utility function from the apollo-link package to transform your Promise to an Observable.
import React from "react";
import { AppRegistry } from 'react-native';
import { onError } from "apollo-link-error";
import { fromPromise, ApolloLink } from "apollo-link";
import { ApolloClient } from "apollo-client";
let apolloClient;
const getNewToken = () => {
return apolloClient.query({ query: GET_TOKEN_QUERY }).then((response) => {
// extract your accessToken from your response data and return it
const { accessToken } = response.data;
return accessToken;
});
};
const errorLink = onError(
({ graphQLErrors, networkError, operation, forward }) => {
if (graphQLErrors) {
for (let err of graphQLErrors) {
switch (err.extensions.code) {
case "UNAUTHENTICATED":
return fromPromise(
getNewToken().catch((error) => {
// Handle token refresh errors e.g clear stored tokens, redirect to login
return;
})
)
.filter((value) => Boolean(value))
.flatMap((accessToken) => {
const oldHeaders = operation.getContext().headers;
// modify the operation context with a new token
operation.setContext({
headers: {
...oldHeaders,
authorization: `Bearer ${accessToken}`,
},
});
// retry the request, returning the new observable
return forward(operation);
});
}
}
}
}
);
apolloClient = new ApolloClient({
link: ApolloLink.from([errorLink, authLink, httpLink]),
});
const App = () => (
<ApolloProvider client={apolloClient}>
<MyRootComponent />
</ApolloProvider>
);
AppRegistry.registerComponent('MyApplication', () => App);
You can stop at the above implementation which worked correctly until two or more requests failed concurrently. So, to handle concurrent requests failure on token expiration, have a look at this post.
Update - Jan 2022
you can see basic React JWT Authentication Setup from: https://github.com/bilguun-zorigt/React-GraphQL-JWT-Authentication-Example
I've also added the safety points to consider when setting up authentication on both the frontend and backend on the Readme section of the repository. (XSS attack, csrf attack etc...)
Original answer - Dec 2021
My solution:
Works with concurrent requests (by using single promise for all requests)
Doesn't wait for error to happen
Used second client for refresh mutation
import { setContext } from '#apollo/client/link/context';
async function getRefreshedAccessTokenPromise() {
try {
const { data } = await apolloClientAuth.mutate({ mutation: REFRESH })
// maybe dispatch result to redux or something
return data.refreshToken.token
} catch (error) {
// logout, show alert or something
return error
}
}
let pendingAccessTokenPromise = null
export function getAccessTokenPromise() {
const authTokenState = reduxStoreMain.getState().authToken
const currentNumericDate = Math.round(Date.now() / 1000)
if (authTokenState && authTokenState.token && authTokenState.payload &&
currentNumericDate + 1 * 60 <= authTokenState.payload.exp) {
//if (currentNumericDate + 3 * 60 >= authTokenState.payload.exp) getRefreshedAccessTokenPromise()
return new Promise(resolve => resolve(authTokenState.token))
}
if (!pendingAccessTokenPromise) pendingAccessTokenPromise = getRefreshedAccessTokenPromise().finally(() => pendingAccessTokenPromise = null)
return pendingAccessTokenPromise
}
export const linkTokenHeader = setContext(async (_, { headers }) => {
const accessToken = await getAccessTokenPromise()
return {
headers: {
...headers,
Authorization: accessToken ? `JWT ${accessToken}` : '',
}
}
})
export const apolloClientMain = new ApolloClient({
link: ApolloLink.from([
linkError,
linkTokenHeader,
linkMain
]),
cache: inMemoryCache
});
If you are using JWT, you should be able to detect when your JWT token is about to expire or if it is already expired.
Therefore, you do not need to make a request that will always fail with 401 unauthorized.
You can simplify the implementation this way:
const REFRESH_TOKEN_LEGROOM = 5 * 60
export function getTokenState(token?: string | null) {
if (!token) {
return { valid: false, needRefresh: true }
}
const decoded = decode(token)
if (!decoded) {
return { valid: false, needRefresh: true }
} else if (decoded.exp && (timestamp() + REFRESH_TOKEN_LEGROOM) > decoded.exp) {
return { valid: true, needRefresh: true }
} else {
return { valid: true, needRefresh: false }
}
}
export let apolloClient : ApolloClient<NormalizedCacheObject>
const refreshAuthToken = async () => {
return apolloClient.mutate({
mutation: gql```
query refreshAuthToken {
refreshAuthToken {
value
}```,
}).then((res) => {
const newAccessToken = res.data?.refreshAuthToken?.value
localStorage.setString('accessToken', newAccessToken);
return newAccessToken
})
}
const apolloHttpLink = createHttpLink({
uri: Config.graphqlUrl
})
const apolloAuthLink = setContext(async (request, { headers }) => {
// set token as refreshToken for refreshing token request
if (request.operationName === 'refreshAuthToken') {
let refreshToken = localStorage.getString("refreshToken")
if (refreshToken) {
return {
headers: {
...headers,
authorization: `Bearer ${refreshToken}`,
}
}
} else {
return { headers }
}
}
let token = localStorage.getString("accessToken")
const tokenState = getTokenState(token)
if (token && tokenState.needRefresh) {
const refreshPromise = refreshAuthToken()
if (tokenState.valid === false) {
token = await refreshPromise
}
}
if (token) {
return {
headers: {
...headers,
authorization: `Bearer ${token}`,
}
}
} else {
return { headers }
}
})
apolloClient = new ApolloClient({
link: apolloAuthLink.concat(apolloHttpLink),
cache: new InMemoryCache()
})
The advantage of this implementation:
If the access token is about to expire (REFRESH_TOKEN_LEGROOM), it will request a refresh token without stopping the current query. Which should be invisible to your user
If the access token is already expired, it will refresh the token and wait for the response to update it. Much faster than waiting for the error back
The disadvantage:
If you make many requests at once, it may request several times a refresh. You can easily protect against it by waiting a global promise for example. But you will have to implement a proper race condition check if you want to guaranty only one refresh.
after checking this topic and some others very good on internet, my code worked with the following solution
ApolloClient,
NormalizedCacheObject,
gql,
createHttpLink,
InMemoryCache,
} from '#apollo/client';
import { setContext } from '#apollo/client/link/context';
import jwt_decode, { JwtPayload } from 'jwt-decode';
import {
getStorageData,
setStorageData,
STORAGE_CONTANTS,
} from '../utils/local';
export function isRefreshNeeded(token?: string | null) {
if (!token) {
return { valid: false, needRefresh: true };
}
const decoded = jwt_decode<JwtPayload>(token);
if (!decoded) {
return { valid: false, needRefresh: true };
}
if (decoded.exp && Date.now() >= decoded.exp * 1000) {
return { valid: false, needRefresh: true };
}
return { valid: true, needRefresh: false };
}
export let client: ApolloClient<NormalizedCacheObject>;
const refreshAuthToken = async () => {
const refreshToken = getStorageData(STORAGE_CONTANTS.REFRESHTOKEN);
const newToken = await client
.mutate({
mutation: gql`
mutation RefreshToken($refreshAccessTokenRefreshToken: String!) {
refreshAccessToken(refreshToken: $refreshAccessTokenRefreshToken) {
accessToken
status
}
}
`,
variables: { refreshAccessTokenRefreshToken: refreshToken },
})
.then(res => {
const newAccessToken = res.data?.refreshAccessToken?.accessToken;
setStorageData(STORAGE_CONTANTS.AUTHTOKEN, newAccessToken, true);
return newAccessToken;
});
return newToken;
};
const apolloHttpLink = createHttpLink({
uri: process.env.REACT_APP_API_URL,
});
const apolloAuthLink = setContext(async (request, { headers }) => {
if (request.operationName !== 'RefreshToken') {
let token = getStorageData(STORAGE_CONTANTS.AUTHTOKEN);
const shouldRefresh = isRefreshNeeded(token);
if (token && shouldRefresh.needRefresh) {
const refreshPromise = await refreshAuthToken();
if (shouldRefresh.valid === false) {
token = await refreshPromise;
}
}
if (token) {
return {
headers: {
...headers,
authorization: `${token}`,
},
};
}
return { headers };
}
return { headers };
});
client = new ApolloClient({
link: apolloAuthLink.concat(apolloHttpLink),
cache: new InMemoryCache(),
});
A much simpler solution is using RetryLink. retryIf supports async operations so one could do something like this:
class GraphQLClient {
constructor() {
const httpLink = new HttpLink({ uri: '<graphql-endpoint>', fetch: fetch })
const authLink = setContext((_, { headers }) => this._getAuthHeaders(headers))
const retryLink = new RetryLink({
delay: { initial: 300, max: Infinity, jitter: false },
attempts: {
max: 3,
retryIf: (error, operation) => this._handleRetry(error, operation)
}})
this.client = new ApolloClient({
link: ApolloLink.from([ authLink, retryLink, httpLink ]),
cache: new InMemoryCache()
})
}
async _handleRetry(error, operation) {
let requiresRetry = false
if (error.statusCode === 401) {
requiresRetry = true
if (!this.refreshingToken) {
this.refreshingToken = true
await this.requestNewAccessToken()
operation.setContext(({ headers = {} }) => this._getAuthHeaders(headers))
this.refreshingToken = false
}
}
return requiresRetry
}
async requestNewAccessToken() {
// get new access token
}
_getAuthHeaders(headers) {
// return headers
}
}

React Native, fetch after async function

I have a problem that I do not know how to solve...
I have an api token saved in AsyncStorage, and when I want do fetch to rest I need this api token, but I do not know how to do it.
I have file Functions.js with AsyncStorage functions.
async retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
return item;
} catch (error) {
console.warn(error.message);
}
},
getApiToken: function(){
try {
return Functions.retrieveItem('api_token');
} catch (error) {
console.warn(error.message);
}
},
File with fetch functions. (Api.js)
I tested with an asynchronous function, but not found...
async get(url) {
try {
var api = await Functions.getApiToken();
if (!api)
api = "";
let opt = {
method: 'get',
headers: new Headers({
'Content-Type': 'application/x-www-form-urlencoded'
}),
};
return fetch(baseUrl + url + api, opt);
} catch (error){
console.warn(error);
}
},
When I did the fetch function, it worked for me without problems
And the screen file Home.js
componentDidMount() {
Api.get('home').then( response => {
this.setState({
profiles: response.profiles,
});
});
}
Please modify your Functions.js code a bit -
async retrieveItem(key) {
try {
const retrievedItem = await AsyncStorage.getItem(key);
const item = JSON.parse(retrievedItem);
return item;
} catch (error) {
console.warn(error.message);
}
},
async getApiToken{
try {
let token = await retrieveItem('api_token');
return token;
} catch (error) {
console.warn(error.message);
}
}
Hope this helps you!