What you’ll take away
- A time step is shared clock arithmetic; looking at a code does not start a new full interval.
- A verifier can consider earlier steps, but time eligibility does not establish complete acceptance.
- A matching output can be rejected after prior successful use even while its step remains eligible.
- Short manually entered OTPs do not provide every property of a multifactor or phishing-resistant protocol.
- A logged-in session has separate continuity and timeout rules; code turnover alone does not end or renew it.
The changing code and the signed-in session have different clocks
An authenticator displays a code that changes on a short schedule. After using it to sign in, you may remain in the account while many more codes appear. That does not by itself mean the old code is still being accepted. TOTP derives a code from a time counter, while a service can maintain a separate session after authentication. [1][4] The code's generation interval and the account's continuing session are different pieces of the sign-in process.
There are four useful questions: which time step generates the code, which steps the verifier currently considers, whether a matching code has already been used, and whether an established session is still valid. We will separate them with original paper examples. RFC 6238 and its HOTP foundation, RFC 4226, are informational documents from 2011 and 2005; the newer verifier and session guidance here is NIST SP 800-63B-4. [1][2][3][4] No account, secret or authentication request is used. The invented acceptance rules and session durations illustrate relationships; they are not settings recommended for a real service.
Distinguish the TOTP moving factor from verifier acceptance state and authenticated-session state. TOTP computes HOTP(K,T), where T is derived from time. [1] A successful authentication can establish a session whose continuity uses a separate session secret and timeout policy. [4] Consequently, a code changing on the authenticator does not imply that an existing session must terminate, nor does a continuing session prove that the previously submitted OTP remains acceptable for another authentication.
Our analysis separates four quantities: the generation step, the candidate-step set evaluated by the verifier, prior successful use during validity, and session validity. The algorithm sources are informational RFC 6238, May 2011, and RFC 4226, December 2005. [1][2] The verifier and session distinctions use the cited sections of NIST SP 800-63B-4; those are parts of one publication, not independent implementations. [3][4] All time values, candidate rules and session durations below are hypothetical. No cryptographic key, real code, network authentication or implementation benchmark is involved. The goal is to interpret the timing relationships without turning one clock into a substitute for every other check.
The short code is derived from a shared secret and a moving input
TOTP does not require the verifier to send each displayed code to the authenticator. Its documented construction lets both sides derive the expected value from shared secret material and agreed parameters. [1][2] The long-lived secret and the short displayed digits do different jobs. The secret is an input to generation; the displayed value is an output used in an authentication exchange. A countdown beside that output is therefore not a countdown to replacing the underlying secret.
The moving input also distinguishes TOTP from its HOTP foundation. HOTP uses an event counter; TOTP substitutes a counter calculated from time. [1] With the same secret, algorithm and time step, recalculating during that step gives the same output. Opening the app again does not create a new step in this model. We will call the outputs for steps 300 and 301 C300 and C301. Those are labels, not usable codes. For the few steps in our worked timing cases, assume the outputs are different so that we can follow which step a submitted value represents.
The HOTP construction combines a shared symmetric key with a moving counter and reduces the result to a user-entered representation. [2] RFC 6238 replaces the event counter with T=floor((t−T0)/X), while retaining HOTP as its building block. [1] The verifier can compute expected outputs because it has the corresponding secret material and parameters. The OTP value is not the long-term key, and moving from one time step to another does not rotate that key.
For fixed K, agreed algorithm and formatting parameters, HOTP(K,T) is deterministic. Repeated generation at the same T therefore returns the same value. [1][2] We use C300 and C301 only as symbolic output labels and stipulate distinct outputs over the small range involved in the examples. This assumption avoids treating a short decimal output as a globally unique counter identifier. We do not instantiate K or execute a cryptographic algorithm. The timing conclusions follow from the counter calculation and declared candidate sets, leaving real secret establishment, protection and full verifier implementation outside this article.
A thirty-second step does not begin when you look at the screen
RFC 6238 recommends a default time step of 30 seconds and defines a default starting reference of the Unix epoch. [1] Use those parameters for our paper clock, with timestamps measured in seconds after that reference. At timestamp 9,000, dividing by 30 gives step 300. At 9,029, the division is 300 with a remainder, so the step is still 300. At 9,030, the counter reaches 301. The boundaries belong to the shared time calculation, not to the moment a person opens an authenticator.
Suppose someone first reads C300 at 9,012. Eighteen seconds remain until that generation step ends. Someone reading the same value at 9,029 has only one second until the boundary. Both saw a code from a thirty-second step; neither started a new thirty-second interval by looking. The interval is 9,000 inclusive to 9,030 exclusive. This describes generation only. Whether the verifier will still consider that step shortly afterward is a separate policy question, and whether the value has already been used is another.
Set X=30 seconds and T0=0, the default parameters described in RFC 6238. [1] Our original timestamps are small illustrative Unix-second values, not current wall-clock readings. T(9000)=300, T(9029)=300 and T(9030)=301. In general, step n occupies the half-open interval [30n,30(n+1)) under these parameters. The floor operation creates a shared boundary; it is not a rolling lifetime measured from the user's first observation. Refreshing the display does not move either edge of that interval.
For a reading at t=9012, the remaining generation time is 9030−9012=18 seconds; at t=9029 it is 1 second. Both observations have T=300. The remaining interval depends on phase within the step, even with identical step length and secret. The output remains deterministic within that step, but generation-window membership alone does not define verifier acceptance. We will retain the half-open convention throughout: t=9030 belongs to 301, not 300. This makes the subsequent arrival-time and acceptance-window examples precise at their boundaries rather than relying on a vague thirty-second expiry label.
The verifier can consider an earlier step after the display changes
A code can be read near a boundary and arrive after it. RFC 6238 discusses this difference between generation and receiving time, and calls for a bounded validation policy rather than assuming the two timestamps are identical. [1] For our invented verifier, consider the current step and the immediately previous step. This is a teaching rule, not a statement about every authenticator service. Other checks remain separate, and the few symbolic code values are assumed distinct.
Read C300 at 9,029 and let the request arrive at 9,031. Only two seconds have passed, but the verifier is now in step 301. A current-step-only rule would not include 300. Our current-plus-previous rule considers {300,301}, so C300 is still time-eligible. This says nothing yet about prior use or the rest of authentication. Conversely, C299 is outside that candidate set even if someone submits it at the same arrival time. The useful comparison is between the generating step and the verifier's candidate steps, not simply between two countdown displays.
RFC 6238 distinguishes generation time from receipt time and discusses an acceptable transmission-delay window. [1] Define our original candidate policy W(t_s)={T_s−1,T_s}, where T_s=floor(t_s/30) is the verifier's step at receipt. This deliberately simple past-and-current rule illustrates timing; it is not a deployment recommendation or a claim of full NIST conformance. We assume distinct outputs for the nearby counters and defer successful-use state to a later section.
At generator time 9029, T_p=300. At receipt time 9031, T_s=301 and W={300,301}. Hence 300∈W despite the two-second delay crossing a generation boundary. Under W_current={301}, the same generated step would be outside the set. Step 299 is outside both sets at that receipt time. These are candidate-membership results, not complete authentication decisions. The receiver has not learned an exact generation timestamp merely by matching an output; within a step, the same output can be generated at multiple times. [1]
A one-second clock difference does not always mean one step of difference
Clock disagreement is separate from delivery delay. RFC 6238 discusses bounded allowances for drift, and NIST's OTP guidance requires a defined lifetime that considers drift, entry time and network delay. [1][3] A fixed difference in seconds does not translate into a fixed difference in the rounded-down step numbers. Whether it crosses a boundary depends on where both clocks are within the step. That distinction explains why the same small clock difference can affect two nearby attempts differently.
Keep our paper verifier and give the authenticator a clock one second ahead, with no transmission delay in this comparison. When the verifier reads 9,028, the authenticator reads 9,029; both calculate 300. One second later, the verifier reads 9,029 while the authenticator reads 9,030: the counters are 300 and 301. Our past-and-current verifier rule does not include that future 301. At verifier time 9,031, the authenticator reads 9,032 and both have 301 again. The unchanged one-second offset has produced step differences of zero, one and zero; the arithmetic does not require the clock error itself to change.
Let the prover clock be t_p=t_s+δ with fixed δ=1 second and zero transport delay in this original comparison. The step difference is floor((t_s+1)/30)−floor(t_s/30), which depends on the fractional position within the verifier's step. It is not δ/X rounded once and applied as a constant offset. RFC 6238 discusses drift limits and resynchronization, while NIST describes accounting for expected drift and entry/network allowances. [1][3] We are evaluating a timing model, not prescribing a resynchronization procedure.
At t_s=9028, (T_s,T_p)=(300,300); at 9029 the pair is (300,301); at 9031 it is (301,301). Under W(t_s)={T_s−1,T_s}, the middle prover step is excluded and the other two are included. The constant one-second lead is enough to cause this phase-dependent difference. Allowing future candidates would change the membership rule, but its scope and security implications would require a real verifier policy. The example does not justify widening a production window, disabling limits or inferring an account's actual tolerance from a single successful or failed entry.
A code’s time eligibility can span more than its generation step
Follow step 300 itself through our same verifier rule. It is the current step from 9,000 up to, but not including, 9,030. It is the previous step from 9,030 up to 9,060. Thus it remains a candidate for a total of 60 seconds, even though it is generated for one 30-second step. At 9,060 the verifier considers 301 and 302 instead. This interval follows from our declared current-plus-previous rule, not a universal sixty-second lifetime for authenticator codes.
The sixty-second span also does not mean every person gets sixty seconds after reading the display. Reading at 9,012 leaves 48 seconds until 9,060; reading at 9,029 leaves 31. These are remaining time-eligibility intervals only. Prior successful use can make a matching value unacceptable earlier, and other authentication requirements can still fail. RFC 6238 separates bounded validation from one-time acceptance. [1] Keeping the words time-eligible attached to this calculation prevents a useful clock example from becoming a false promise that a code will always work until its final candidate boundary.
For fixed n=300 under W(t_s)={T_s−1,T_s}, membership requires T_s∈{300,301}. Therefore the verifier-time eligibility set is [9000,9060), of length 60 seconds, whereas the generation interval is [9000,9030), of length 30 seconds. The interval union is continuous here because the two accepted step intervals are adjacent. At exactly 9060, T_s=302 and n=300 is absent from W. The calculation holds the generated step fixed while the receiving clock moves; it does not restart a timer on each submission.
Observation time still matters: 9060−9012=48 seconds and 9060−9029=31 seconds remain for the two readings. These are not universal post-display validity periods. They also ignore replay state by design; complete acceptance cannot be inferred from a membership interval alone. RFC 6238 requires rejecting a second use after successful validation, and NIST requires once-only acceptance during validity. [1][3] The calculation isolates the time predicate so that its limits remain visible. It neither chooses a production acceptance window nor asserts that every matching six-digit string uniquely identifies n=300.
Matching the expected digits is different from accepting them again
The authenticator can keep displaying the same output during a step even after that output has been used successfully. That does not make a second use acceptable. RFC 6238 says the verifier must not accept a repeated attempt after the first successful validation; NIST likewise requires a given OTP to be accepted only once while valid. [1][3] The verifier therefore needs to enforce more than agreement between the submitted digits and a freshly computed expectation.
In our sequential paper record, C300 is successfully accepted at 9,008 and its successful use is recorded. At 9,010, it still matches step 300, but a second submission is rejected as already used. At 9,035, step 300 remains inside our time-candidate set, yet the earlier successful-use record still prevents acceptance. These later rejections do not contradict the code calculation. They show that a matching output and an unused output are different conditions. The record is an explanatory state sequence; it does not supply a concurrent server implementation or establish how any particular service stores its replay information.
Separate Match from Unused. RFC 6238 prohibits accepting a second attempt of an OTP after its first successful validation, and NIST SP 800-63B-4 requires once-only acceptance while the OTP remains valid. [1][3] Recomputing HOTP(K,T) does not itself implement that requirement: the generation function remains deterministic regardless of whether the verifier previously accepted its output. Relevant verifier state therefore participates in complete acceptance.
Our sequential fixture records successful acceptance of C300 at t=9008. At 9010, n=300 remains current but Unused is false. At 9035, n=300 is still included in W={300,301}, yet Unused remains false. These two later events both fail the stipulated replay check despite a time-eligible match. Assume the nearby output values are distinct and the successful-use state is already effective before each later event. This is not a database design, concurrency test or claim that raw decimal strings are permanent global identifiers. A real implementation must enforce its one-time rule across the actual validation path; a generation formula alone cannot establish that enforcement.
Six digits are a short representation, not an everlasting unique identifier
HOTP's documented construction reduces a larger cryptographic result to a decimal output of a chosen length. [2] With six digits, there are 1,000,000 possible strings, including values with leading zeroes. A formatting example such as 004271 has six digits even though its ordinary integer value is 4,271. This is a made-up display example, not a code generated from a secret or an instruction to enter it anywhere. The short display is designed for entry; its length does not describe the size of the underlying key.
A finite display also cannot assign a different string to every possible counter forever. If 1,000,001 counter inputs are represented by only 1,000,000 six-digit strings, at least two inputs share a string. That counting argument does not locate a collision, predict a neighboring step, or estimate a real attack's success. It explains why our timing examples stated their local distinct-output assumption. NIST also requires rate limiting for single-factor OTP outputs shorter than 64 bits. [3] Output length, bounded attempts and one-time acceptance address related limits; none can be replaced by calling the digits unique.
RFC 4226 defines dynamic truncation followed by reduction modulo 10^Digit. [2] For Digit=6, the representation space has 10^6 strings when zero padding preserves six positions. Our invented formatting value 004271 illustrates representation only; no HMAC evaluation or source test vector is used. The key, counter and decimal output have different roles and sizes, so six visible digits cannot be interpreted as the entropy or length of the long-term shared key.
By the pigeonhole principle, mapping 1,000,001 distinct counter inputs into 1,000,000 six-digit outputs produces at least one repeated output. This is a cardinality statement, not a discovered collision, independence assumption or measured guessing probability. It does not imply that adjacent steps repeat. Our nearby C-labels were explicitly assumed distinct to make candidate membership unambiguous. NIST's single-factor OTP requirements make rate limiting mandatory when the output is shorter than 64 bits. [3] Six decimal digits have fewer than 2^20 possibilities and can be encoded in 20 bits, placing this output below the 64-bit threshold. This identifies the applicable category without selecting an attempt limit or claiming that our paper verifier provides complete security.
One-time use and a second factor do not establish the same property
An authenticator code can be one part of two-factor sign-in without being two factors by itself. NIST describes a single-factor OTP authenticator as proving possession of the authenticator. It separately describes a multifactor OTP authenticator that requires a local activation factor for each use. [3] Typing a displayed code does not turn that possession proof into a memorized-password factor merely because it passed through a person's memory or keyboard. The factor depends on the mechanism being proved.
Replay resistance is also different from phishing resistance. NIST does not classify manually entered OTP authentication as phishing-resistant, because manual entry does not bind the output to the specific session being authenticated. [3] A short lifetime and rejection after successful use remain useful constraints, but they do not establish that missing binding. This distinction is enough for the current article: we do not reproduce a passkey migration or recovery guide. When describing an authenticator, say which property it provides and which authentication step uses it, instead of treating the changing display as proof of every account-security property.
NIST distinguishes single-factor OTP possession from multifactor OTP activated by a knowledge or biometric factor, with activation required on each use of the latter. [3] Factor classification is a property of the authenticator and protocol, not the number of UI fields or the fact that a human typed an output. A separate password plus an appropriate OTP possession check can play different roles in a wider authentication flow; the TOTP counter formula alone does not specify that complete flow.
The same source treats manually entered OTPs as lacking phishing resistance because their outputs are not bound to the specific session being authenticated. [3] Replay prevention, freshness and session binding must therefore retain separate meanings. Rejecting C300 after successful consumption proves only the stipulated one-time behavior in our example; it does not establish verifier-name or channel binding. This is a scope distinction supported by the guidance, not a demonstration against a live service. The existing passkey articles develop cryptographic origin/challenge binding; here the focus remains the limits of time and successful-use state in interpreting a manually entered short code.
After authentication, a session carries a different kind of continuity
A service can start an authenticated session after a successful sign-in and use a separate session secret to recognize continuing interactions. NIST's session guidance describes that arrangement and treats browser cookies as one possible session mechanism. [4] The session is not created again every time the authenticator's code changes. This explains how someone can remain signed in while the original OTP has become both used and too old for another authentication attempt.
It also explains the reverse situation. A session may end while the authenticator continues generating perfectly ordinary codes. The session ending has not, by itself, changed the authenticator's shared secret or stopped its clock. These conclusions follow from the separate states in the described model. NIST distinguishes termination, reauthentication and session continuity. [4] A new displayed code is not evidence that an earlier session remains active; an active session is not evidence that the earlier code may be reused. Ask which record the service is evaluating before interpreting either observation.
NIST's session model uses a secret issued by the session host at authentication to establish continuity between client software and the service. [4] That session secret is distinct from the long-term OTP key and the submitted short OTP output. The document describes browser cookies as a session mechanism and explicitly separates session management from repeated authentication. [4] This gives three different objects to keep apart: the authenticator secret, a generated code and the continuing session's binding material.
The original timing fixture can therefore have C300 already consumed and outside W while a session established after its successful use remains active. Conversely, a terminated session can coexist with continued TOTP generation under the same secret and time parameters. Neither observation contradicts the other because the states have different transition rules. The article does not prescribe token storage, cookie settings or federation behavior. It uses the documented distinction to reject an unsupported inference: code turnover alone is neither a session-termination event nor proof of a session's continued validity.
Activity can move an idle deadline without moving the overall deadline
NIST distinguishes an inactivity timeout from an overall session timeout. Activity resets the former, while successful reauthentication resets both in the described model. [4] For a separate invented example, start a session at 10:00 with a twenty-minute overall limit and a five-minute inactivity limit. These chosen durations are not NIST's compliance limits. The initial deadlines are 10:20 overall and 10:05 idle. Activity at 10:03 moves the idle deadline to 10:08; activity at 10:07 moves it to 10:12.
If nothing else happens, the session ends at 10:12 under that idle rule. In a different continuation with sufficiently frequent activity through 10:19, the idle deadline reaches 10:24 but the overall deadline remains 10:20, so that earlier limit ends the session. A third continuation successfully reauthenticates at 10:18 while still active: its new overall deadline is 10:38 and its idle deadline is 10:23. The difference between ordinary activity and reauthentication changes the arithmetic. None of these deadlines is obtained by counting how many thirty-second authenticator codes have appeared.
Model session validity using an overall deadline D_o and an inactivity deadline D_i, terminating when either is reached. NIST's described semantics reset D_i on activity and both deadlines on successful reauthentication. [4] In our separate wall-clock example, start at 10:00 with invented limits 20 minutes and 5 minutes. Initially (D_o,D_i)=(10:20,10:05). Activity at 10:03 and 10:07 produces D_i=10:08 and 10:12 respectively, with the original D_o unchanged at 10:20.
Without subsequent activity, the minimum deadline is 10:12. In an alternative continuation, stipulate intervening activity frequent enough to avoid idle expiry, with the last event at 10:19: D_i=10:24, but D_o=10:20 still terminates the session first. In a third valid continuation, successful reauthentication at 10:18 sets (D_o,D_i)=(10:38,10:23). These durations illustrate distinct transitions rather than prescribing assurance-level limits or behavior for every consumer service. The OTP generation counter is not an input to either session deadline. Counting code rotations would therefore be the wrong calculation for this session policy.
Identify which event succeeded before explaining what remains valid
When an authenticator experience seems inconsistent, describe the actual event. A code appearing proves that the app displayed an output. A time-eligible match answers a narrower question than a completed sign-in. A successful sign-in may establish a session, and later page activity can belong to that session without accepting the original code again. These distinctions follow the generation, verifier and session mechanisms in the cited documents. [1][3][4] They make the observations compatible without assuming that every continued page view reuses an OTP.
For an account owner, use the service's supported sign-in and session controls and keep an unsuccessful attempt separate from a known successful one. For a maintainer, distinguish the relevant counter window, successful-use result, authentication completion and session expiry in the explanation. The examples do not require publishing keys or recording usable codes in a diagnostic report. A clear report can say that a step was outside the declared window, that a matching value was already consumed, or that an idle session ended. Those are different conclusions, and the changing digits alone do not choose among them.
Use separate outcome categories for generation, time-candidate match, successful-use enforcement, complete authentication and session continuation. The RFC timing construction and NIST verifier/session guidance support those different boundaries. [1][3][4] A time predicate can be true while a replay predicate is false; both can pass while some other requirement of a larger authentication flow fails. A later valid session request can occur without evaluating the original OTP at all. Each conclusion needs evidence from the mechanism actually making that decision.
A bounded explanation should identify the relevant time basis and event sequence, without exposing key material or treating a short decimal output as a permanent account identifier. Preserve whether authentication actually completed, whether the request was a repeat of an already successful use, and whether the current interaction belongs to an existing session. The original examples provide counter arithmetic and state transitions, not a deployable verifier. Their practical result is an interpretable account of validity: a generation interval, an acceptance window, a one-time decision and a session lifetime can overlap without becoming the same interval or the same permission.
Sources & further reading
Explore the references behind this guide. External pages may change after our review.
- RFC 6238 — TOTP: Time-Based One-Time Password Algorithm (May 2011)IETF / RFC Editor · Accessed 7 Nov 2026
- RFC 4226 — HOTP: An HMAC-Based One-Time Password Algorithm (December 2005)IETF / RFC Editor · Accessed 7 Nov 2026
- AuthenticatorsNIST · Accessed 7 Nov 2026
- Session ManagementNIST · Accessed 7 Nov 2026
Article updated 7 Nov 2026.
Found something we should revisit?
Good explanations get better with thoughtful feedback.