34

another noob question. I'm logging in my user to the system using JWT authorization, getting the token and saving it in localstorage and then sending a post request that saves data (its a big form basically). Problem is, the sever is invalidating the token after a given time (20 minutes or so) and so, some of my post requests are returning 401 status. How to verify (and if needed, show a login prompt) before sending the post request? I'm using redux-form to make my forms.

P.S: I know I'm supposed to use action creators and such, but I'm still a newbie, so not very good at those stuff.

here's my authentication:

export function loginUser(creds) {

const data = querystring.stringify({_username: creds.username, _password: creds.password});

let config = {
    method: 'POST',
    headers: { 'Content-Type':'application/x-www-form-urlencoded' },
    body: data
};

return dispatch => {
    // We dispatch requestLogin to kickoff the call to the API
    dispatch(requestLogin(creds));

    return fetch(BASE_URL+'/login_check', config)
        .then(response =>
            response.json().then(user => ({ user, response }))
        ).then(({ user, response }) =>  {
            if (!response.ok) {
                // If there was a problem, we want to
                // dispatch the error condition
                dispatch(loginError(user.message));
                return Promise.reject(user)
            } else {
                // If login was successful, set the token in local storage
                localStorage.setItem('id_token', user.token);
                let token = localStorage.getItem('id_token')
                console.log(token);
                // Dispatch the success action
                dispatch(receiveLogin(user));
            }
        }).catch(err => console.log("Error: ", err))
    }
}

and here's the POST request (I'm getting the values object from redux-form)

const token = localStorage.getItem('id_token');
const AuthStr = 'Bearer '.concat(token);

let headers ={
headers: { 'Content-Type':'application/json','Authorization' : AuthStr }
};

export default (async function showResults(values, dispatch) {
axios.post(BASE_URL + '/new', values, headers)
    .then(function (response) {
        console.log(values);
        console.log(response);
    })
    .catch(function (error) {
        console.log(token);
        console.log(values)
        console.log(error.response);
    });
});

P.P.S: if anyone has any suggestion for improving my code, feel free to comment.

Samia Ruponti
  • 3,910
  • 12
  • 42
  • 62

3 Answers3

35

Here is a solution with jwt-decode library by comparing the exp attributes in the JWT token with current time. (JWT token is simply a Base64 encoded string)

  • Install jwt-decode (npm install jwt-decode --save)

     let token = localStorage.getItem(TOKEN);
      let decodedToken = jwt_decode(token);
      console.log("Decoded Token", decodedToken);
      let currentDate = new Date();
    
      // JWT exp is in seconds
      if (decodedToken.exp * 1000 < currentDate.getTime()) {
        console.log("Token expired.");
      } else {
        console.log("Valid token");   
        result = true;
      }
    

IMPORTANT: jwt-decode doesn't validate the token, any well formed JWT can be decoded. You should validate the token in your server-side logic by using something like express-jwt, koa-jwt, Owin Bearer JWT, etc.

jfk
  • 4,335
  • 34
  • 27
  • 2
    I think this is more valid than the accepted answer. `jwt-decode` is made by the same author of `jsonwebtoken` which is being mentioned in the accepted answer and if you are going to only `decode`(and you have to) the `token` from the browser(client-side), this library is made for that purpose. – wonsuc Feb 16 '21 at 02:09
34

JWT expiration can be checked in two ways. First of all you have to install jsonwebtoken package and require it at the top of your file. Thereafter, you can follow the below ways to check JWT expiration before sending any rest requests.

Option 1

var isExpired = false;
const token = localStorage.getItem('id_token');
var decodedToken=jwt.decode(token, {complete: true});
var dateNow = new Date();

if(decodedToken.exp < dateNow.getTime())
    isExpired = true;

Option 2

const token = localStorage.getItem('id_token');
jwt.verify(token, 'shhhhh', function(err, decoded) {
  if (err) {
    /*
      err = {
        name: 'TokenExpiredError',
        message: 'jwt expired',
        expiredAt: 1408621000
      }
    */
  }
});

Check the error of that method. If it is the TokenExpiredError then that means the token is expired.

Suvethan Nantha
  • 2,404
  • 16
  • 28
  • 22
    Are you pretty sure about the solution 2 ? That means that the front will have the secret key to check the token. Then looking at the source code, you can find it and use it to generate valide tokens – Pi Home Server Mar 28 '18 at 09:17
  • @PiHomeServer Yes actually based on the question asked I answered and gave two ways of fixing it. But having said that we can still improve the way we check the JWT expiration. – Suvethan Nantha Mar 29 '18 at 04:58
  • @SamiaRuponti if the answer I provided is correct for your situation can you please upvote my answer? If it's not perfect,I'm also learning so tell me what went wrong. Thanks. – Suvethan Nantha Mar 29 '18 at 05:01
  • 2
    I actually followed neither option. As Pi Home Server said, that's not very secure, so ended up doing some server-side validation thing. Thank you though. – Samia Ruponti Mar 29 '18 at 05:11
  • @SamiaRuponti Yes of course, server side validations are always secure, as per the question I answered you. Anyway thanks for the feedback. – Suvethan Nantha Mar 29 '18 at 05:16
  • 1
    As per RFC 7519, `exp` claim is a `NumericDate` value, which is defined as a number of **seconds**. `Date`'s `getTime()` method returns **milliseconds**. – Rares Oct 08 '19 at 16:01
  • 1
    @SamiaRuponti it is not insecure to decode JWT on client side. Anybody who have your JWT token can decode it. There's even online decoder you can use. Thats why you only store insecure information like userid, expiration date etc. Method one is usable. Method 2 is not. – Someone Special May 20 '20 at 06:06
  • 4
    @SamiaRuponti Just to clarify for you about JWT tokens. JWT tokens are simply base64 encoded so anyone can "decode" the token to see what claims are present within the token. https://jwt.io/ This site is a great resource for exploring that. The third section of a JWT is the signature, which is signed and verified only using the secret key stored on the server. The secret key should _always_ be kept safe, but theoretically you could simply check the exp (expiration) header within the JWT without needing to communicate with the server. – Brian Sizemore May 27 '20 at 22:36
  • Thank you. this is a very old question though. – Samia Ruponti Jun 05 '20 at 07:13
  • 1
    Thanks for Option1. The only update I had to make is to convert the exp to millis (*1000) or reduce .getTime() by 1000 to do comparison. Not sure if somebody have this similar issue. – PravyNandas Jul 24 '20 at 20:28
8

You could also use a middleWare to check if the token has been expired. You even could update the token if it will almost expire. For example, you could do something shown below;

 export function jwtMiddleware({ dispatch, getState }) {
  return (next) => (action) => {
    switch (action.type) {
      case 'CHECK_AUTH_TOKEN' :
        if (getState().auth && getState().auth.token) {
          var tokenExpiration = jwtDecode(getState().auth.token).exp;
          var tokenExpirationTimeInSeconds = (tokenExpiration - moment(Math.floor(Date.now() / 1000)));
          if (tokenExpiration && tokenExpirationTimeInSeconds < 20) {
            history.push(i18next.t('translation:routes.auth.logout'));
          }
        }
      break;
      case 'UPDATE_AUTH_TOKEN' :
        if (getState().auth && getState().auth.token) {
          var tokenExpiration = jwtDecode(getState().auth.token).exp;
          var tokenExpirationTimeInSeconds = (tokenExpiration - moment(Math.floor(Date.now() / 1000)));
          if (tokenExpiration && tokenExpirationTimeInSeconds < 100 && tokenExpirationTimeInSeconds > 20) {
            if (!getState().auth.fetching) {
              return dispatch(refreshAuthToken(getState().auth));
            }
          }
        }
      break;
      case 'REFRESH_AUTH_TOKEN_FAIL' :
        if (getState().auth && getState().auth.token) {
          return dispatch(removeAuthToken(getState().auth)).then(response => {
            history.push(i18next.t('translation:routes.auth.logout'));
          });
        }
      break;
      }
    return next(action);
  }
}
directory
  • 3,093
  • 8
  • 45
  • 85