Worth adding to the practical tips: since the verify window accepts a few valid codes at once and a 6-digit space is only a million values, per-account attempt throttling matters as much as the window size. Also, unlike a password, the TOTP secret has to be recoverable by the server to compute codes, so it usually needs encryption at rest with a key kept outside the database, rather than hashing. Are recovery codes something you plan to cover? Storing those hashed and single-use is the part people often skip.