AzureAD / microsoft-authentication-library-for-js

Microsoft Authentication Library (MSAL) for JS
http://aka.ms/aadv2
MIT License
3.64k stars 2.65k forks source link

How to handle client clock being skewed, not synced with actual time? #3070

Closed thoo1 closed 3 years ago

thoo1 commented 3 years ago

Library

Description

Is there any guidance on how to deal with client clock being skewed. Eg imagine a scenario where the client's clock thinks it is currently 18:00 UTC and has a cached token that is valid until 19:00 UTC. The client app then makes an api call using the cached token since it thinks it is still valid. However, the actual time is 20:00 UTC, and the server rejects the request with a 401 because the token is expired (the token expires around 19:00 UTC, and the current time is 20:00 UTC)

We see evidence of this happening in our current application, where the client side log's show the request being made at an incorrect time like 18:36 UTC (the timestamp is from client side clock) but when we search up the request in backend logs, the time is 19:32 UTC and the client had passed a token that is valid until 19:00 UTC.

I was wondering if this scenario has been encountered before, if so, is there any guidance on how to handle such situations? In my opinion, it looks like the solution boils down to the client app being aware that the time is incorrect and should react to that (display some error message or force fetch a new token)

Thanks!

Source

pkanher617 commented 3 years ago

@thoo1 Are we talking about access tokens or refresh tokens? If it's access tokens, the token expiration date returned in the response should be calculated based on the client's time. Tokens are valid by default for 1 hour from when they are issued. When you call acquireTokenSilent, it will check the current expiration time based on the client side calculation and renew the token if it is past this time.

We see evidence of this happening in our current application, where the client side log's show the request being made at an incorrect time like 18:36 UTC (the timestamp is from client side clock) but when we search up the request in backend logs, the time is 19:32 UTC and the client had passed a token that is valid until 19:00 UTC

This shouldn't be the case. So lets say the client's clock is skewed 1 hr before the server's clock (using your example). If the client acquired a token at 18:00, the token should have a exp claim, assuming 1hr token lifetime, based on the server's clock of 20:00 in its claims (19:00 + 1hr) (double checking this, but pretty sure that this is how it works), but MSAL will set the expiration date to 19:00. When the token is validated, it should be based on the time of the server's clock.

Are you seeing different behavior? Check the exp claim of the token.

image In the above image you can see there is a slight difference in the expiration times - this is because the expiration used by MSAL is calculated separately than the one from the auth server.

There is a tokenRenewalOffsetSeconds configuration option but this is usually to offset seconds or minutes, not an entire hour.

The client app then makes an api call using the cached token since it thinks it is still valid

How does this happen? The client should be using acquireTokenSilent to retrieve a new token to make sure that the token is still valid. If it isn't valid, acquireTokenSilent should be renewing the token. acquireTokenSilent does this based on the client clock, not the exp claim in the token.

the server rejects the request with a 401

By "the server" do you mean the resource server? How does the token validation happen?

thoo1 commented 3 years ago

Hi @pkanher617 thanks for taking your time to look into this. I can answer some of your questions

The client app then makes an api call using the cached token since it thinks it is still valid How does this happen? The client should be using acquireTokenSilent to retrieve a new token to make sure that the token is still valid. If it isn't valid, acquireTokenSilent should be renewing the token. acquireTokenSilent does this based on the client clock, not the exp claim in the token.

Sorry for the confusion in my wording, when I said client app I meant the msal-library, we do just use acquireTokenSilent and don't do anything special on top. I meant to express that msal-browser library thought the token was still valid (as it looks at the client clock, not the exp claim) and our code sent the request with the token. Since the client clock was about an hour behind, the token in reality was expired and the server had rejected it.

This shouldn't be the case. So lets say the client's clock is skewed 1 hr before the server's clock (using your example). If the client acquired a token at 18:00, the token should have a exp claim, assuming 1hr token lifetime, based on the server's clock of 20:00 in its claims (19:00 + 1hr) (double checking this, but pretty sure that this is how it works), but MSAL will set the expiration date to 19:00. When the token is validated, it should be based on the time of the server's clock.

I understand what you're saying, and I agree. If the client clock is consistently 1 hour behind the server, that is fine as long as long as the server validates the token using the exp claim (which will be based on server time). Let me validate this with the backend team, that they are using the exp claim (I don't see any other claim that could be used).

There is one small nuance. What happens if the client clock is not consistently 1 hour behind? I'll see if I can validate this in the logs first before I raise this topic further

Thanks for your answer! I'll do a bit more digging

pkanher617 commented 3 years ago

I think we need to be really clear here about which actors are performing specific actions. There is a client, which calls ATS to get tokens. This client should never be sending expired tokens. ATS always calculates lifetimes based on the client's clock, so it should never return a token that has passed the lifetime.

There is a resource server, which validates the access token. If this service is out of sync, there are going to be issues no matter what.

Finally, there is the auth server which is the one that accepts the login credentials and issues tokens.

Since the client clock was about an hour behind, the token in reality was expired and the server had rejected it.

If the token was used within an hour of when it was issued, the only way it would be expired on the service but not the client would be if the client clock is somehow moving slower than the server's clock, or the resource service is also skewed. Expiration for ATS is calculated based on lifetime, not exact timestamps.

There is one small nuance. What happens if the client clock is not consistently 1 hour behind? I'll see if I can validate this in the logs first before I raise this topic further

Again, the expiration timestamp claim of the token is always based on the auth server's clock, not the client. It depends on the resource service validating the token. If that resource service is out of sync, there isn't much we can do. As I said above, the only way the client would be sending expired tokens is if the clock is somehow moving at a different rate than the auth server's clock. Meaning, 1hr on the service is >1hr on the client.

tnorling commented 3 years ago

Closing as this doesn't appear to be a problem with the library. Please open a new issue if you determine there is any action we need to take, thanks!