You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Version 1.2.6 introduced a bug in PR #1657 where clean_old_tokens uses incompatible arithmetic compared to TokenFactory.expiry, causing newly created tokens to be immediately filtered out when token_lifespan is set to variable-length durations like 6.months.
Root Cause
Two methods calculate "N months from now" using different arithmetic:
See attached minimal reproduction case that demonstrates:
Time frozen at March 1st, 2026 for consistent reproduction
The arithmetic mismatch (1.38 day difference)
Newly created token being filtered out
The exact calculation difference
cd dta-bug-reproduction
bundle install
ruby reproduce_bug.rb
The script uses travel_to to freeze time at March 1st, 2026, ensuring the bug is consistently reproducible regardless of when the script is run.
Proposed Fix
Change line 265 in app/models/devise_token_auth/concerns/user.rb to use calendar arithmetic:
# Before (buggy):max_lifespan_expiry=Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i# After (fixed):max_lifespan_expiry=(Time.zone.now + DeviseTokenAuth.token_lifespan).to_i
This makes clean_old_tokens use the same arithmetic as TokenFactory.expiry.
Additional Issues
Inconsistent Time methods:TokenFactory.expiry uses Time.zone.now while clean_old_tokens uses Time.now (cosmetic but should be consistent)
Tests don't catch this: The test suite uses fixed durations (2.weeks, 1.week) where to_i conversion is lossless, and constructs expiries with the same arithmetic as clean_old_tokens, so the mismatch never appears in tests.
Environment
devise_token_auth: 1.2.6
Rails: 8.0
Ruby: 3.3.7
Configuration:config.token_lifespan = 6.months
Workarounds
Downgrade to v1.2.5 (recommended for now)
Increase max_number_of_devices to prevent cleanup from triggering (doesn't fix root cause)
Use fixed duration like 180.days instead of 6.months
Override clean_old_tokens in User model with corrected arithmetic
This is a minimal reproduction case for the Duration arithmetic mismatch bug in devise_token_auth 1.2.6.
Bug Summary
When token_lifespan is set to a variable-length duration like 6.months, newly created tokens are immediately filtered out by clean_old_tokens due to incompatible arithmetic:
Freezes time at March 1st, 2026 (fixed date for consistent reproduction)
Configures token_lifespan = 6.months and max_number_of_devices = 2
Demonstrates the arithmetic mismatch (1.38 day difference from March 1)
Creates 3 tokens to trigger cleanup
Checks if the newly created token survived
Shows PASS ✅ or FAIL ❌ with detailed explanation
Expected Output
v1.2.5 (Working)
✅ TEST PASSED - Token cleanup works correctly
v1.2.6 (Buggy)
❌ TEST FAILED - Newly created token was immediately filtered out
Reason: Token expiry (1756684800) > Filter cutoff (1756565676)
Difference: 119124 seconds (1.38 days)
Note: Using fixed date March 1st, 2026 for consistent reproduction
Files
Dockerfile - Ruby 3.3.7 container setup
docker-compose.yml - Docker Compose configuration
Gemfile - Minimal dependencies
reproduce_bug.rb - Standalone reproduction script
test_both_versions.sh - Shell script to test both versions
README.md - This file
GITHUB_ISSUE.md - GitHub issue template
Root Cause
PR #1657 (commit 9719d24) introduced a bug in clean_old_tokens at line 265:
# Buggy (v1.2.6):max_lifespan_expiry=Time.now.to_i + DeviseTokenAuth.token_lifespan.to_i# Should be (like TokenFactory.expiry):max_lifespan_expiry=(Time.zone.now + DeviseTokenAuth.token_lifespan).to_i
The difference:
6.months.to_i = 15,778,476 seconds = 182.62 days (average)
Calendar March → September = 184.0 days
Mismatch: 1.38 days
Impact
Affected: Configurations using variable-length durations (6.months, 1.year)
Not affected: Fixed durations (2.weeks, 30.days)
Symptom: Password resets fail when max_number_of_devices limit is reached
Introduced: v1.2.6
Last working: v1.2.5
Workarounds
Downgrade to v1.2.5 (recommended)
Increase max_number_of_devices to prevent cleanup
Use fixed duration like 180.days instead of 6.months
Override clean_old_tokens in User model with corrected arithmetic
Summary
Version 1.2.6 introduced a bug in PR #1657 where
clean_old_tokensuses incompatible arithmetic compared toTokenFactory.expiry, causing newly created tokens to be immediately filtered out whentoken_lifespanis set to variable-length durations like6.months.Root Cause
Two methods calculate "N months from now" using different arithmetic:
1.
TokenFactory.expiry(lib/devise_token_auth/token_factory.rb:64):2.
clean_old_tokens(app/models/devise_token_auth/concerns/user.rb:265):The Problem
When
token_lifespan = 6.months:6.months.to_i= 15,778,476 seconds = 182.62 days (average month length)This causes the filter at line 268 to reject the newly created token:
Impact
6.months,1.yearin certain months)2.weeks,30.days)NoMethodErrorwhenmax_number_of_deviceslimit is reachedReproduction
See attached minimal reproduction case that demonstrates:
cd dta-bug-reproduction bundle install ruby reproduce_bug.rbThe script uses
travel_toto freeze time at March 1st, 2026, ensuring the bug is consistently reproducible regardless of when the script is run.Proposed Fix
Change line 265 in
app/models/devise_token_auth/concerns/user.rbto use calendar arithmetic:This makes
clean_old_tokensuse the same arithmetic asTokenFactory.expiry.Additional Issues
Inconsistent Time methods:
TokenFactory.expiryusesTime.zone.nowwhileclean_old_tokensusesTime.now(cosmetic but should be consistent)Tests don't catch this: The test suite uses fixed durations (
2.weeks,1.week) whereto_iconversion is lossless, and constructs expiries with the same arithmetic asclean_old_tokens, so the mismatch never appears in tests.Environment
config.token_lifespan = 6.monthsWorkarounds
max_number_of_devicesto prevent cleanup from triggering (doesn't fix root cause)180.daysinstead of6.monthsclean_old_tokensin User model with corrected arithmeticReferences
app/models/devise_token_auth/concerns/user.rblines 260-281devise_token_auth 1.2.6 Bug Reproduction
This is a minimal reproduction case for the Duration arithmetic mismatch bug in devise_token_auth 1.2.6.
Bug Summary
When
token_lifespanis set to a variable-length duration like6.months, newly created tokens are immediately filtered out byclean_old_tokensdue to incompatible arithmetic:TokenFactory.expiryuses calendar arithmetic:(Time.zone.now + 6.months).to_iclean_old_tokensuses fixed-second arithmetic:Time.now.to_i + 6.months.to_iThis creates a ~1.38 day mismatch (from March 1), causing the new token to be rejected.
Setup & Run
Option 1: Using Docker (Recommended)
Test both versions side-by-side:
This will:
Option 2: Local Ruby
Test a specific version:
# Edit Gemfile to set version (1.2.5 or 1.2.6) bundle install ruby reproduce_bug.rbTest both versions automatically:
Requirements:
What the Script Does
The reproduction script:
token_lifespan = 6.monthsandmax_number_of_devices = 2Expected Output
v1.2.5 (Working)
v1.2.6 (Buggy)
Files
Dockerfile- Ruby 3.3.7 container setupdocker-compose.yml- Docker Compose configurationGemfile- Minimal dependenciesreproduce_bug.rb- Standalone reproduction scripttest_both_versions.sh- Shell script to test both versionsREADME.md- This fileGITHUB_ISSUE.md- GitHub issue templateRoot Cause
PR #1657 (commit 9719d24) introduced a bug in
clean_old_tokensat line 265:The difference:
6.months.to_i= 15,778,476 seconds = 182.62 days (average)Impact
6.months,1.year)2.weeks,30.days)max_number_of_deviceslimit is reachedWorkarounds
max_number_of_devicesto prevent cleanup180.daysinstead of6.monthsclean_old_tokensin User model with corrected arithmetic