In practice, clocks are often out of sync enough that you want to give some leeway and accept some codes on either side of the current code. The implementation I use for sourcehut is similarly tiny:
Yes, in fact the RFCs for both HOTP and TOTP have a section each dedicated to resynchronization of counter/clock to account for client and server being out of sync. Here are the relevant sections:
In case of HOTP, the client's counter could be ahead of the server's counter if a user requests multiple HOTPs from the client before the user presents the HOTP to the server. Therefore the server should look ahead according to a permissible look-ahead window to see if any succeeding HOTP value matches the HOTP presented by the user or client.
In case of TOTP, there is of course the problem of clock drifts. Therefore the server should look backward as well as forward by a few time steps to see if any TOTP backward or forward matches the TOTP presented by the user or client.
In the past, when I've implemented TOTP on the server, I've allowed for a couple of time periods of drift. But I recorded the time period that last matched, and only allowed subsequent authentication attempts to be strictly greater than the last successful time period, to prevent replay attacks.
> Note that a prover may send the same OTP inside a given time-step window multiple times to a verifier. The verifier MUST NOT accept the second attempt of the OTP after the successful validation has been issued for the first OTP, which ensures one-time only use of an OTP.
https://git.sr.ht/~sircmpwn/meta.sr.ht/tree/master/metasrht/...