ably / ably-js

Javascript, Node, Typescript, React, React Native client library SDK for Ably realtime messaging service
https://ably.com/download
Apache License 2.0
313 stars 55 forks source link

Error: Invalid token; issuedAt time must not be in the future. #1720

Closed nikitadubyk closed 5 months ago

nikitadubyk commented 5 months ago

Hi! Recently, I have encountered a problem that I often get an error - Uncaught (in promise) Error: Invalid token; issuedAt time must not be in the future. (See https://help.ably.io/error/40001 for help.) At the same time, I checked the performance of ably with this error - everything works well, no problems. I decrypted the token I receive from backend, there is an issueAt field inside the token

OS: Windows 10 Pro 22H2 Version of ably: 2.0.1

image image

┆Issue is synchronized with this Jira Task by Unito

VeskeR commented 5 months ago

Hi @nikitadubyk ! Are you using queryTime: true option when initializing your Ably Client? If not, could you please try that and see if that fixes your issue? You can do it like this:

new Ably.Realtime({ ...other options, queryTime: true });
// or if using Rest client
new Ably.Rest({ ...other options, queryTime: true });

By default, the library will use the local computer time to generate the current timestamp and, as a result, the "issued" field in the token. So, the problem may be coming from the clock synchronization issue between your server/client computers and Ably servers. If your clock is ahead by a significant enough difference, you may end up sending to Ably servers a token that was issued in the future. When using queryTime: true, the library will query Ably servers for the current time instead of relying on locally available time, thus eliminating any possible synchronization issues.

nikitadubyk commented 5 months ago

@VeskeR I tried adding queryTime: true to Ably.Realtime as specified in your example and it didn't work, still the error remains

VeskeR commented 5 months ago

Hey @nikitadubyk , I would need more information about your current setup to troubleshoot this. Could you please provide a code example of how you are initializing your Ably client on the frontend? And a code example of how you are initializing the Ably client on the backend and generating the token there?

Also, previously when mentioning queryTime: true, I forgot to specify that this option should be passed to whichever Ably client instance is responsible for generating an Ably token. In your case, queryTime: true should be passed to the Ably client instance that is generating a token on the backend. Passing this option to the Ably client instance on the frontend won't make a difference, since frontend is using a token generated by the backend.

nikitadubyk commented 5 months ago

@VeskeR this is how a new Realtime is created on the backend

new Realtime({
      queryTime: true,
      key: config.apiKey,
      clientId: config.clientId,
    });

and this is how Realtime is created on the frontend. I also tried adding queryTime: true to the frontend along with the backend - didn't help.

const ablyToken = request for get ably token;
new Realtime({
          logLevel: 2,
          clientId: name,
          useTokenAuth: true,
          token: ablyToken.data,
        });

and this is how generated ably token on backend

 payload with '*': ['publish', 'subscribe', 'presence'],
 jwt.sign(payload, keySecret, jwtOptions, (error, token) => {
      if (error) {
        logger.error('Ably Token: Could not sign jwt token', error);
        next(error);
        return;
      }

      res.setHeader('Content-Type', 'application/json');
      res.send(JSON.stringify(token));
    });
nikitadubyk commented 5 months ago

@VeskeR I also noticed that with issueAt error this message appears in the console image

VeskeR commented 5 months ago

Hey @nikitadubyk , thank you for providing more information!

Based on the code snippet you provided:

jwt.sign(payload, keySecret, jwtOptions, (error, token) => { ... })

I assume you're using jsonwebtoken module to generate JWT tokens for Ably auth, following this guide from Ably.

jsonwebtoken automatically includes iat (issued at) claim upon signing the token, and it is based on the current time on the machine. In your case, it appears that the machine signing those tokens (whether it's your local machine during development or your server) has an inaccurate clock and is running ahead of Ably servers. Thus, tokens are signed with an iat that is in the future, resulting in the error: Invalid token; issuedAt time must not be in the future when trying to use this token in your frontend client.

Ideally you should ensure that your server has an accurate clock, using something like NTP daemon. If this is not something you can change, then you can override the value used for iat claim by jsonwebtoken by adding it to your payload object, see example in jsonwebtoken docs%20%2D%2030%20%7D%2C%20%27shhhhh%27)%3B). Your payload will look something like this:

const payload = {
  // other props...
  iat: Math.floor(Date.now() / 1000) - 5, // backdate by 5 seconds
};

If this fixes the issue for you, it would confirm that the problem arises from an inaccurate clock on your server. To obtain better timestamps for iat, you can also calculate the time offset between your server and Ably servers by querying the current time on Ably servers using the https://rest.ably.io/time endpoint. You can easily do this using the Ably.Rest instance like this:

const ablyRest = new Ably.Rest({ key });
const ablyServerTime = await ablyRest.time();
const offset = ablyServerTime - Date.now();

And then use that stored offset when providing iat in the payload:

const payload = {
  // other props...
  iat: Math.floor((Date.now() + offset) / 1000),
};
nikitadubyk commented 5 months ago

@VeskeR thanks, that helped solve the problem!