[GH-ISSUE #3515] TOTP Delay Window #9629

Closed
opened 2026-04-13 05:12:37 -05:00 by GiteaMirror · 0 comments
Owner

Originally created by @Hysterelius on GitHub (Jul 21, 2025).
Original GitHub issue: https://github.com/better-auth/better-auth/issues/3515

Is this suited for github?

  • Yes, this is suited for github

Currently with better-auth when verifying the TOTP it checks in a specified range like so,

       T-30         T           T+30     
                                         
+--------------+------------+-----------+
| α code       | β code     | γ code    |
+--------------+------------+-----------+
                     ^                   
                     |                   
                     |                   
                 accepted                            

Where β code would be the only accepted code, as the server time (T) is currently producing the given code, and would be the only accepted code for the user to input.

Describe the solution you'd like

IETF recommends that both α code & γ code would also be accepted as valid codes to account for differences in server time and user time, so having a rolling window of accepting codes from a time step either side of the accepted code. Like so,

       T-30         T           T+30      
                                          
+--------------+------------+-----------+ 
| α code       | β code     | γ code    | 
+--------------+------------+-----------+ 
       ^             ^            ^       
       |             |            |       
       |             |            |       
also accepted    accepted    also accepted

This would make the user experience better as if there is a delay from getting the code to entering it would not mark as incorrect.

Based on this study and with the default rate limit of 3 requests per 10 seconds, the code guess ability be like so,

Code acceptable window 1 3 5
Guess probability in 24 hours for 6 digits 2.55% 7.48% 12.15%
... for 8 digits 0.025% 0.077% 0.129%
My maths

The attacker has:

  • 3 guesses per 10 seconds
  • There are 24\times 60 \times 6 \times 3 = 25\, 920 guesses in 24 hours

The formula for success is (based off the binomial),

P(\text{success})= 1 - P(\text{no successful guesses}) P(\text{success}) = 1 - (1-p)^n

With p the probability of success, and d being the number of accepted digits,

p = \frac{\text{number of acceptable codes}}{10^d}

So,

P(\text{success}) = 1 - (1-\frac{\text{number of acceptable codes}}{10^d})^{25\, 920}

Describe alternatives you've considered

Instead of a having a rolling code window, there could be a rolling time window where it only accepts codes from a certain time period from the timestamp.

       T-30         T           T+30     
                                         
┌──────────────┌────────────┌───────────┐
│ α code   |   │ β code     │ γ code |  │
└──────────▲───└────────────└────────▲──┘
           └─────────────────────────┘   
                accept a range of 60s    

however, this would likely be more complex and just checking a rolling window would achieve the same outcome.

Additional context

I am happy to implement this in a PR, as I am pretty familiar with TS. I thought it would best have a discussion first about whether to default all users to a window of 1 or to 3 (as that is probably what they intend), and the configuration options around it.

Also, I really love the library!

Originally created by @Hysterelius on GitHub (Jul 21, 2025). Original GitHub issue: https://github.com/better-auth/better-auth/issues/3515 ### Is this suited for github? - [x] Yes, this is suited for github ### Is your feature request related to a problem? Please describe. Currently with better-auth when verifying the TOTP it checks in a specified range like so, ``` T-30 T T+30 +--------------+------------+-----------+ | α code | β code | γ code | +--------------+------------+-----------+ ^ | | accepted ``` Where `β code` would be the only accepted code, as the server time (`T`) is currently producing the given code, and would be the only accepted code for the user to input. ### Describe the solution you'd like [IETF recommends](https://datatracker.ietf.org/doc/html/rfc6238#:~:text=We%20RECOMMEND%20that%20at%20most%20one%0A%20%20%20time%20step%20is%20allowed%20as%20the%20network%20delay.) that both `α code` & `γ code` would also be accepted as valid codes to account for differences in server time and user time, so having a rolling window of accepting codes from a time step either side of the accepted code. Like so, ``` T-30 T T+30 +--------------+------------+-----------+ | α code | β code | γ code | +--------------+------------+-----------+ ^ ^ ^ | | | | | | also accepted accepted also accepted ``` This would make the user experience better as if there is a delay from getting the code to entering it would not mark as incorrect. Based on this [study](https://pulsesecurity.co.nz/articles/totp-bruting) and with the default rate limit of 3 requests per 10 seconds, the code guess ability be like so, | Code acceptable window | 1 | 3 | 5 | |--------------------------------------------|--------|--------|--------| | Guess probability in 24 hours for 6 digits | 2.55% | 7.48% | 12.15% | | ... for 8 digits | 0.025% | 0.077% | 0.129% | <details> <summary>My maths</summary> The attacker has: - 3 guesses per 10 seconds - There are $24\times 60 \times 6 \times 3 = 25\, 920$ guesses in 24 hours The formula for success is (based off the binomial), $$P(\text{success})= 1 - P(\text{no successful guesses})$$ $$P(\text{success}) = 1 - (1-p)^n$$ With $p$ the probability of success, and $d$ being the number of accepted digits, $$p = \frac{\text{number of acceptable codes}}{10^d}$$ So, $$P(\text{success}) = 1 - (1-\frac{\text{number of acceptable codes}}{10^d})^{25\, 920}$$ </details> ### Describe alternatives you've considered Instead of a having a rolling code window, there could be a rolling time window where it only accepts codes from a certain time period from the timestamp. ``` T-30 T T+30 ┌──────────────┌────────────┌───────────┐ │ α code | │ β code │ γ code | │ └──────────▲───└────────────└────────▲──┘ └─────────────────────────┘ accept a range of 60s ``` however, this would likely be more complex and just checking a rolling window would achieve the same outcome. ### Additional context I am happy to implement this in a PR, as I am pretty familiar with TS. I thought it would best have a discussion first about whether to default all users to a window of 1 or to 3 (as that is probably what they intend), and the configuration options around it. Also, I really love the library!
GiteaMirror added the lockedenhancement labels 2026-04-13 05:12:37 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/better-auth#9629