0

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?

Somebody
  • 347
  • 2
  • 14

1 Answers1

0

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();
Somebody
  • 347
  • 2
  • 14
  • I ended up doing something similar because I also noticed react-admin would not call authProvider.checkAuth() consistently (e.g. when using the ). One weakness with the approach though is that if a component uses multiple resources, react-admin will make the requests to the dataProvider in parallel. If the access token is expired, this will generate multiple API requests (e.g. /auth/refreshtoken) to get a new one using the same refresh token. Ideally only one /auth/refreshtoken call would be made for the main component, before rendering. – greenkarmic Dec 03 '21 at 20:49
  • 1
    I fixed the earlier problem I mentioned by using a singleton fetch promise for my /auth/refreshtoken call. Works perfectly, all dataProvider requests await for the same/single refreshtoken fetch request. Based on this idea here: https://medium.com/tomincode/singleton-promises-c750dc40eaa7 – greenkarmic Dec 03 '21 at 21:26