Size the RFC 6238 TOTP acceptance window from the time step and drift tolerance, and see the clock skew it forgives.
TOTP is HOTP with the counter set to floor((current time − T0) ÷ X), so validation means recomputing the code for the current step. Because client and server clocks drift, RFC 6238 lets the server also test one step either side, which triples the acceptance window to 90 seconds at the standard 30-second step and triples the false-accept probability with it. Every extra drift window you allow to placate users with bad clocks widens both the replay window for a phished code and the odds of a blind guess, so the tolerance should be a deliberate decision rather than a default nobody read.
TOTP Validity
acceptance window = time step × (2 × drift windows + 1); clock skew tolerated = drift windows × time step.
False-accept probability
P(random code accepted) = steps accepted ÷ 10^digits.
acceptance window = time step × (2 × drift windows + 1); clock skew tolerated = drift windows × time step. TOTP is HOTP with the counter set to floor((current time − T0) ÷ X), so validation means recomputing the code for the current step. Because client and server clocks drift, RFC 6238 lets the server also test one step either side, which triples the acceptance window to 90 seconds at the standard 30-second step and triples the false-accept probability with it.
Every extra drift window you allow to placate users with bad clocks widens both the replay window for a phished code and the odds of a blind guess, so the tolerance should be a deliberate decision rather than a default nobody read.
This calculator takes 4 inputs: Time step (X), Drift windows accepted each side, Client clock skew, Code length. The pre-filled defaults are a realistic starting point — replace them with figures from your own environment for a result you can act on.
The server accepts the previous, current and next step. A code the user typed just as the step rolled over still validates, which is the whole point — but it also means a code stays replayable for up to 90 seconds after it was generated.
Do not widen the window for everyone. RFC 6238 suggests recording a per-client drift offset once the user has successfully authenticated, then applying that offset on later validations so the shared window stays narrow.