Basically, the server gives me a token object. I am planning to check the token on the client if it is expired/valid before making a request to the server.

The function below works as expected. I just want to know if my code catch-all edge cases? I don't want, even 1 second that will request will fail due to an expired access token.

I am using moment for comparing dates. I also converted the current date and expiry date to UTC so that I'm making sure that they are in the same timezone before comparing them.

const moment = require('moment');

/**
The token object passed in the function looks like this:
{
access_token: '2hbssMdXDpwQX5WcnZ-iJlO754MLkEeDCmF-f1A-MaU',
token_type: 'Bearer',
expires_in: 604800,
refresh_token: 'VxnN9uBVIcNMpuwRVpvXo2YxWuNFEayHqfnCM7aCTSI',
scope: 'public',
created_at: 1603604241
}
*/

export default function tokenValid(token = {}) {
const currentDate = moment().utc();

return currentDate < expiryDate;
}


EDIT (based on @hjpotter92's comment)

export default function tokenValid(token = {}) {
const currentDate = moment().unix();
const expiryDate = token.created_at + token.expires_in;

return currentDate < expiryDate;
}

• epoch (or unix time) is always timezone insensitive. you can treat it as a normal number. Oct 25, 2020 at 6:56
• @hjpotter92 I have updated the code based on your comment. Oct 25, 2020 at 7:28

Welcome to Code Review!

As I already mentioned in comments, epoch values can be treated as normal numerals, without having to involve momentjs or other external libraries.

In js, you can get current epoch value in milliseconds accuracy using Date.now(). The value in token is with seconds accuracy. It is only a factor of 1000.

## Rewrite

/**
The token object passed in the function looks like this:
{
access_token: '2hbssMdXDpwQX5WcnZ-iJlO754MLkEeDCmF-f1A-MaU',
token_type: 'Bearer',
expires_in: 604800,
refresh_token: 'VxnN9uBVIcNMpuwRVpvXo2YxWuNFEayHqfnCM7aCTSI',
scope: 'public',
created_at: 1603604241
}
*/

export default function tokenValid(token = {}) {
const now = Date.now() / 1000;
const expiry = token.created_at + token.expires_in;
return now < expiry;
}


## EDIT

In response to the comment:

Changed

// from
parseInt(Date.now() / 1000)
// to
Date.now() / 1000


In the case here, Math.floor or Math.round are more suitable. parseInt is usually called when you want to convert from string to integer. We already know that Date.now() is giving a number and not a string. Also, the conversion/rounding is not really needed here.

• I have seen that most use Math.floor() to remove the millisecond in Date.now() instead of parseInt, is there any advantages on using parseInt? Oct 25, 2020 at 7:47
• I also changed this token.created_at + token.expires_in to token.created_at + token.expires_in - 60, the 60 seconds is for fail-safe. The use case would be if there is only 1 second before the expiration time the client will mark it as valid, but if the request to server took longer that 1 second, it will be expired when it reaches the server. Oct 25, 2020 at 8:03

You seem to ignore two facts.

1. The client clock can be out of sync with the server clock.

2. The transport of the request from client to server takes more then zero time.

Both these cases mean that no matter how hard you try on the client side, there is always a chance that the server will evaluate the token as expired during its processing.

What you should do is have the client take the same action when server refuses the token as you do when your client side validation fails.

In general, client side validation

• is not necesary
• is not reliable
• reduces network traffic
• increases user experience

Whereas backend side validation

• is necesary
• is the only source of truth

Clients should understand and handle possible server side validation errors prior to deploying their own validation logic.