Skip to content
TDACorpIDENTITYSign in

Clock skew

Which checks depend on your server's clock, how much difference each tolerates, the symptoms of a wrong clock, and how to measure it.

On this page

OAuth is full of short lifetimes, and a lifetime is a comparison against a clock. If your server's clock is wrong by more than a check tolerates, good requests are refused, and the error usually names a token or a proof and never the real cause. This page lists every check that uses your clock and how much it forgives.

Run a time synchronization service on every server that talks to identity. That is the whole fix. The rest of this page is for knowing when to suspect it.

Checks that use your clock#

CheckWhereTolerance
Client assertion lifetimeIdentity checks exp against its own clock.With the SDK default of 60 seconds: you may be about 60 seconds behind and about 240 seconds ahead. See below.
DPoP proof iatIdentity, on the pushed authorization, token, revocation, introspection and userinfo endpoints, and your API.Up to 60 seconds either way.
Access token exp and nbfYour API, using the SDK.5 seconds.
ID token exp and iatYour application, using the SDK.60 seconds.
max_age against auth_timeYour application.The same 60 seconds.
Back-channel logout token exp and iatYour application, using the SDK.60 seconds.

Anything identity measures in its own clock, such as the up to 60 seconds a code or a pushed request lives, is not affected by yours.

The figures for identity's own checks (the DPoP proof window, the client assertion lifetime, the lifetime of a code or a pushed request) are ceilings. TDACorp, which runs identity, can shorten them and cannot lengthen them. A shorter window is stricter, so keep your clock well inside the figures here.

The client assertion in detail#

The SDK signs an assertion with exp set to your clock plus 60 seconds by default. Identity refuses it if exp is already behind its own clock, and refuses it if exp is more than 300 seconds ahead of its own clock. With the default lifetime that gives the window above. A longer lifetime tolerates more lag and less lead: at 300 seconds you may be 300 seconds behind and not at all ahead. Raising the lifetime is not a fix. See Client authentication.

Nonces are not your clock#

The DPoP nonce is a value identity makes from its own clock and gives to you. You never compute it, so a skewed clock does not break it. It does not rescue a proof whose iat is wrong.

Symptoms#

You seeSuspect
invalid_client on every pushed authorization or token request, while the key and kid are rightThe assertion is expired or too far ahead on identity's clock.
invalid_dpop_proof with the description about the iat windowThe proof's iat is more than 60 seconds (or less, if identity has shortened the window) from identity's clock.
Your API refuses brand new tokens as invalid_token, but accepts them a few seconds laterYour clock is more than 5 seconds behind, so the token's nbf looks like the future.
ID token verification fails with exp or iatYour clock is more than a minute off.
Everything works on your laptop and fails on a server or in a containerThe server clock. Containers share the host's, and a virtual machine that was paused or restored can have a stale one.

These errors are worth checking against the clock early because nothing else in the error points at it.

Measure it#

Every response from identity carries a Date header. Compare it with your own clock:

curl -sI https://identity.tdacorp.in/.well-known/jwks.json | grep -i '^date'; date -u

The two times should agree within a second or two. Do the same from the machine that fails, not from your laptop. A difference of more than a few seconds is the problem.

Fix it#

  • Run NTP, chrony or your platform's time service, and alert when it is not synchronized.
  • Do not widen tolerances to hide a bad clock. The SDK's relying party, resource-server verifier and decision client have a clockToleranceSeconds option, and it is a way to absorb a known small difference, not to paper over a server that is minutes off. It must be a whole number from 0 to 300; anything else is refused when the client is created rather than clamped, so a mistake shows up at start-up.
  • In tests, pass a clock to the SDK (the relying party, service client, resource server and decision client all take one) so lifetimes can be tested without waiting. Never ship a fixed clock.

Verified against commit 5d85077