10

I checked all the similar questions here but none has what I need. I'm securing the routs in my App and sending the JWT with every request and everything is fine here. The issue is when the JWT expires, instead of logging out the user, I need to know how to refresh that token and keep the user logged in.

Everyone is talking about creating a "Middleware" that handles that, but no one says how to create that middleware and what's in it?

So, what is the best practice in doing that? Should I check for JWT expiration date before sending any request? or should I wait for a "401" response then try to refresh the token (which I don't know how to do), or what exactly?

If anyone has a working example of such a middleware or a package or a project on Github that can help me with this it would be great.

I'm only interested in the front-end part of the process, what to send from react and what should I expect to receive and what to do with it.

Ruby
  • 2,207
  • 12
  • 42
  • 71
  • Maybe that answer will provide you with some usefull info: [JWT-example](https://stackoverflow.com/questions/46364199/any-complete-example-for-express-jwt/52721909#52721909) – iLuvLogix Feb 05 '19 at 10:00
  • If you want the token not to expire, set the maximum expiration time possible (in some cases you can use a '0' for infinite - but I think that was ommited at least with [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) ) and refresh it using a certain routine. To refresh the token your API needs a endpoint that receives a valid, not expired JWT and returns the same signed JWT with the newly set expiration. – iLuvLogix Feb 05 '19 at 10:21

2 Answers2

17

If you are using Axios (which I highly recommend), you can declare your token refreshing behaviours in the response's interceptors. This will apply to all https requests made by Axios.

The process is something like

  1. Checking if the error status is 401
    • If there is a valid refresh token: use it to get the access token
    • if there is no valid refresh token: log the user out and return
  2. Redo the request again with the new token.

Here is an example:

axios.interceptors.response.use(
  (response) => {
    return response
  },
  (error) => {
    return new Promise((resolve) => {
      const originalRequest = error.config
      const refreshToken = localStorage.get('refresh_token')
      if (error.response && error.response.status === 401 && error.config && !error.config.__isRetryRequest && refreshToken) {
        originalRequest._retry = true

        const response = fetch(api.refreshToken, {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json',
          },
          body: JSON.stringify({
            refresh: refreshToken,
          }),
        })
          .then((res) => res.json())
          .then((res) => {
            localStorage.set(res.access, 'token')

            return axios(originalRequest)
          })
        resolve(response)
      }

      return Promise.reject(error)
    })
  },
)
lehoang
  • 3,577
  • 1
  • 15
  • 16
  • 1
    I ended up doing the exact same thing, with different code of course, but the same idea, using Axios interceptors. I accepted your answer as the correct answer. – Ruby Aug 09 '20 at 09:48
  • I am not a fan of that because it promotes using an expired token unless an HTTP call is made. Sending an API request with an expired token is an unnecessary request. I would rather implement an auth service which provides a getToken method. Every time that method gets called, you check if the token has expired. If it expired, refresh it and return the new one. If it hasn‘t, return the one from storage. Use this method in your request interceptor where you add the token to the header. – Tom el Safadi Jul 17 '21 at 03:17
  • 1
    Saving refresh token on client-side is not safe, is it? – JM217 Nov 24 '21 at 18:25
-2

your middelware should look like this block of code (as example you can use whatever you want)

/* eslint-disable */
import request from 'superagent';
function call(meta, token) {
  const method = meta.API_METHOD ? meta.API_METHOD : 'GET';
  let req = request(method, 'http://localhost:8000/' + meta.API_CALL);
  req = req.set({ Authorization: `JWT ${token}` });
  req = meta.API_TYPE ? req.type('Content-Type', meta.API_TYPE) : req.set('Content-Type', 'application/json');
  if (meta.API_PAYLOAD) {
    req = req.send(meta.API_PAYLOAD);
  }
  if (meta.API_QUERY) {
    req.query(meta.API_QUERY);
  }

  return req;
}

export default store => next => action => {
  const state = store.getState();
  const token = state.logged && state.logged.get('token') ?
    state.logged.get('token') : 'eyJhbGciOiJIUzUxMiJ9';
  if (action.meta && action.meta.API_CALL) {
    call(action.meta, token)
      .then((res) => {
        store.dispatch({
          type: action.meta.API_SUCCESS,
          result: res.body,
        });
      })
      .catch(({ status, response }) => {
        if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
          return store.dispatch({
            type: action.meta.API_ERRORS[status],
            result: response.body,
          });
        }
        if (action.meta.API_ERRORS && action.meta.API_ERRORS[status] === '401') {
          /*call the refresh token api*/
          call(<Your Meta for refreshing>, <expiredtoken>)
                .then((res) => {
                    store.dispatch({
                    type: action.meta.API_SUCCESS,
                    result: res.body,
                    });
                })
                .catch(({ status, response }) => {
                    if (action.meta.API_ERRORS && action.meta.API_ERRORS[status]) {
                    return store.dispatch({
                        type: action.meta.API_ERRORS[status],
                        result: response.body,
                    });
                    }
                    throw response;
                });
        }
        throw response;
      });
  }
  return next(action);
};
Anass TIssir
  • 240
  • 2
  • 7