Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Checking expiration #53

Closed
ambs opened this issue Jul 12, 2017 · 20 comments
Closed

Checking expiration #53

ambs opened this issue Jul 12, 2017 · 20 comments

Comments

@ambs
Copy link

ambs commented Jul 12, 2017

As far as I could understand, jwt-decode doesn't check if the token expired, does it?
If yes, how can I check if the token expired?
If not, is there any way to do that easily?
Thanks

@ambs
Copy link
Author

ambs commented Jul 12, 2017

It seems easiest way is to:

	var current_time = new Date().getTime() / 1000;
	if (current_time > jwt.exp) { /* expired */ }

Just leaving this here as it might help anyone.

@cipto-hd
Copy link

Just an alternative:

var current_time = Date.now() / 1000;
if ( jwt.exp < current_time) {
 /* expired */ 
}

@mhombach
Copy link

mhombach commented Dec 18, 2017

Just an important addition: Beware of timezone-Errors.

I think you need to use 'Date.now().valueOf() / 1000;' to get the plain UTC time (UTC is the same format as the 'exp' from the JWT-Token). Otherwise the 'Date.now()' will be converted to you local timezone when comparing, which could be a different one than the jwt-issuer.

Not using 'valueOf()' will in many timezone-related cases invalidate your token and depending on timezone-difference between client and server (or more exact: between jwt-issuer and jwt-validator) you might not be able to use it ever because the timezone-difference is too big.

Edit: I should add as clarification, that this happens because the EXP-Date in the token is not a DATE-Object, it is just a Timestamp-Number which cannot contain any timezone-information. Therefore the EXP in the token will ALWAYS (unless you change it manually) be the neutral timezone (0) UTC and to compare it, you need your time as plain UTC-number too.

You may not encounter this problem at first (when you set the token EXP to 24 hours you will not run in the problem of instant invalidation) but you should still be aware of using the time-comparison correctly.

@davidjb
Copy link

davidjb commented Feb 2, 2018

Also worth mentioning that the exp claim is optional (https://tools.ietf.org/html/rfc7519#section-4.1.4) so you (may) want to handle that use case:

    if (typeof jwt.exp === 'undefined') return 'Never expires!'

There's also nbf for Not Before (https://tools.ietf.org/html/rfc7519#section-4.1.5) as well, which is also optional. If this library were implementing a validity check, which would be helpful, then I suggest it cover more than just "is expired".

@mhombach
Copy link

mhombach commented Feb 2, 2018

@davidjb
i understand your thoughts, but the codeline you wrote is not completely valid from logic.

Just because the exp-attribute isn't set doesn't mean that some custom-attribute for expiration isn't set.
Since both, client and endpoint, should use the same JWT schema, the endpoint should know if it uses the exp-attribute or something custom.
If it uses something custom, then it will check for that instead the exp.
But if the endpoint uses the exp-attribute, then a missing exp-attribute is NOT == "Never Expires". In this case it would be an invalid token because a non-optional setting is missing. And since the server itself would always create tokens with the exp-attribute (so that the endpoint can check for it), this token maybe wasn't even created by the server and may be a try to compromise the system in some way.

@thejohnfreeman
Copy link

thejohnfreeman commented Aug 15, 2018

For the lazy:

function assertAlive (decoded) {
  const now = Date.now().valueOf() / 1000
  if (typeof decoded.exp !== 'undefined' && decoded.exp < now) {
    throw new Error(`token expired: ${JSON.stringify(decoded)}`)
  }
  if (typeof decoded.nbf !== 'undefined' && decoded.nbf > now) {
    throw new Error(`token not yet valid: ${JSON.stringify(decoded)}`)
  }
}

try {
  assertAlive(jwtDecode(token))
} catch (error) {
  console.error(error)
}

@RomanBednyakov
Copy link

RomanBednyakov commented Oct 30, 2018

  isTokenExpired = () => {
    const token = localStorage.getItem("access_token");
    try {
      const date = new Date(0);
      const decoded = decode(token);
      date.setUTCSeconds(decoded.exp);
      return date.valueOf() > new Date().valueOf();
    } catch (err) {
      return false;
    }
  };

@mhombach
Copy link

@RomanBednyakov just some additions to your code:

  • please always try to give some information, why you are posting. Does your code provide us new information? Is it something that wasn‘t possible since some late updates? In what way does your code differ from the previous one?
  • please try to format your code correctly in the github editor, so it get‘s correct syntax-highlighting. This makes it easier for others to read :)
  • you are calling the „new Date“ constructor 3 times, that‘s way more than needed
  • you don‘t need to convert the „exp“ value to a date. Since you only need to compare plain numbers here, that do not contain any information but the value, you can (and should) just compare number a with number b. Number a is already „token.exp“ and number b is „Date.now().valueOf() /1000“. compare both numbers and you will be fine. If you need „Date.now“ in some other function like logging, then save the value to a const or var instead of calling the Date constructor every time.

@mfulton26
Copy link

@mhombach will you help me understand your comment about valueOf?

I think you need to use 'Date.now().valueOf() / 1000;' to get the plain UTC time (UTC is the same format as the 'exp' from the JWT-Token). Otherwise the 'Date.now()' will be converted to you local timezone when comparing, which could be a different one than the jwt-issuer.

Date.now() returns a number representing the milliseconds elapsed since the UNIX epoch (UTC) so why would Date.now().valueOf() / 1000 return anything different than Date.now() / 1000? valueOf “returns the primitive value of the specified object” so if Date.now() already returns a primitive, number, then why would using valueOf with Date.now() make any difference?

Thank you in advance in helping me understand. I'm trying to track down an issue where one my servers is claiming JWTs are expired while the client seems to think it isn't. Anyone with ideas on what to check for in scenarios like this please do share. Thanks!

@vniche
Copy link

vniche commented Feb 4, 2019

@mhombach will you help me understand your comment about valueOf?

I think you need to use 'Date.now().valueOf() / 1000;' to get the plain UTC time (UTC is the same format as the 'exp' from the JWT-Token). Otherwise the 'Date.now()' will be converted to you local timezone when comparing, which could be a different one than the jwt-issuer.

Date.now() returns a number representing the milliseconds elapsed since the UNIX epoch (UTC) so why would Date.now().valueOf() / 1000 return anything different than Date.now() / 1000? valueOf “returns the primitive value of the specified object” so if Date.now() already returns a primitive, number, then why would using valueOf with Date.now() make any difference?

Thank you in advance in helping me understand. I'm trying to track down an issue where one my servers is claiming JWTs are expired while the client seems to think it isn't. Anyone with ideas on what to check for in scenarios like this please do share. Thanks!

@mfulton26 a different date & time on devices (server and client) could have the scenario you mentioned, since runtime grabs the time (now) from the system it's running on.

@mfulton26
Copy link

@vniche that doesn't seem to explain the usage of valueOf() though...

@rstar2
Copy link

rstar2 commented Feb 19, 2019

Using Date.now().valueOf() is completely unnecessary, Date.now() returns a plain Number object which "knows" nothing about time zones and etc...
And second Date.now() already returns the

The Date.now() method returns the number of milliseconds elapsed since January 1, 1970 00:00:00 UTC.

so even no conversionis needed

@mfulton26
Copy link

That was my thinking @rstar2; thank you for confirming.

@mhombach
Copy link

Hmm... i have to admit that i do not 100% understand my thoughts from back when i wrote my first comment.
I just remember that i ran into weird timezone-issues with the token's exp-value and that using .valueOf() did fix the issue.
I just did some tests on a JS playground and i cannot reproduce the problems i got.
I might have been troubled by mixing up a timestamp with seconds and one timestamp with miliseconds, but then again i should have noticed the difference in length...
So long story short: I cannot defend or describe my previous statements. It's just somewhat weird to me, that my comment got many likes and upvotes... Maybe others ran into the same problems i encountered and my "fix" did solve it for them too (regardless of what the core problem was)?
Would be interested in if there were others having also problems related to this issue... :/

@mfulton26
Copy link

@mhombach yes I found it weird and confusing to that your comment got so many likes but the solution didn't help me and I wondered if I was missing something. I appreciate you all taking another look. The issue for me ended up being multiple refresh token requests happening in concurrently which is a separate issue. Thanks!

@emil-malmgaard-rasmussen

Thanks for the help @thejohnfreeman I had some trouble figuring out the logic until i realised how much more cleaner your code were than mine!

@raysk4ever
Copy link

try {
var decoded = await jwt.verify(access_token, config.jwtSecret);
} catch (err) {
if (err instanceof TokenExpiredError) {
var error = errors["ACCESS_TOKEN_EXPIRED"];
var err = new AppError(error.code, error.message, error.responseCode);
return response_manager.send_error_response(res, err);
} else {
var error = errors["INVALID_ACCESS_TOKEN"];
var err = new AppError(error.code, error.message, error.responseCode);
return response_manager.send_error_response(res, err);
}
}

@mhombach
Copy link

try {
var decoded = await jwt.verify(access_token, config.jwtSecret);
} catch (err) {
if (err instanceof TokenExpiredError) {
var error = errors["ACCESS_TOKEN_EXPIRED"];
var err = new AppError(error.code, error.message, error.responseCode);
return response_manager.send_error_response(res, err);
} else {
var error = errors["INVALID_ACCESS_TOKEN"];
var err = new AppError(error.code, error.message, error.responseCode);
return response_manager.send_error_response(res, err);
}
}

  • the issue is closed for some time
  • you didn't write any description to your code, what is it supposed to do? What more is it adding to the already commented solutions?
  • please always post your code in CODE-syntax, so we all get formating and syntax-highlighting

@moonray
Copy link

moonray commented Nov 21, 2019

See https://stackoverflow.com/questions/33184096/date-new-dated-valueof-vs-date-now for why to use .valueOf()
Essentially it's only needed for IE8 backwards compatibility.

@Widcket
Copy link
Contributor

Widcket commented Jan 15, 2020

Hi everyone, if you need to validate ID Tokens please check out the idtoken-verifier library.

@Widcket Widcket closed this as completed Jan 15, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests