[GH-ISSUE #7054] [PM-38955] Android: stale 2FA "Remember" token causes infinite login loop after device trust revocation #127813

Closed
opened 2026-06-13 21:15:04 -05:00 by GiteaMirror · 1 comment
Owner

Originally created by @rootphantomer on GitHub (Jun 12, 2026).
Original GitHub issue: https://github.com/bitwarden/android/issues/7054

Steps To Reproduce

Preconditions

  1. The user account has Two-Factor Authentication (TOTP) enabled
  2. The user previously logged in on the official Bitwarden Android App and checked "Remember me" (trust this device).
  3. The server is NodeWarden (self-hosted Bitwarden-compatible server).

Steps

Step 1 — Admin revokes device trust on the server

DELETE /api/devices/:deviceIdentifier

This cascadingly deletes the device record, all its refresh tokens, and all its 2FA "Remember" tokens.

Step 2 — Wait for access token to expire
The Android App's cached refresh_token has been deleted server-side. Once the access_token expires, the refresh attempt fails and the server logs:

刷新登录失败:令牌不存在或已过期
("Refresh login failed: token does not exist or has expired")

Step 3 — User opens the Android App and attempts to re-login

  1. Enter server URL → email → master password.
  2. The App automatically includes the locally cached (but server-deleted) 2FA "Remember" token in the request:
    {
      "twoFactorProvider": 5,
      "twoFactorToken": "<stale-cached-token>"
    }
    
  3. Server looks up the token via getTrustedTwoFactorDeviceTokenUserId(token, deviceId)not found.
  4. Server returns:
    {
      "error": "invalid_grant",
      "error_description": "Two factor required.",
      "TwoFactorProviders": ["0"],
      "TwoFactorProviders2": {"0": null}
    }
    
  5. App displays the "Two-step login required" screen.

Step 4 — User enters the TOTP code

  1. App sends:
    {
      "twoFactorProvider": 0,
      "twoFactorToken": "<user-entered-totp>"
    }
    
  2. Normal case: TOTP validates → login succeeds.
  3. Bug case: The App does not clear its cached stale "Remember" token, and immediately retries Step 3 (sending the expired token again) → loops infinitely.

Result

The user is unable to log in — stuck in an infinite loop on the 2FA page.

Expected Result

the App should clear the stale "Remember" token after receiving TwoFactorRequired, present the TOTP input, and complete login normally — without entering an infinite loop.

Actual Result

When an admin revokes device trust on the server (deleting the device's refresh tokens and 2FA "Remember" tokens), and the user subsequently tries to log in again on the official Bitwarden Android App:

  1. The App still has the stale/expired 2FA "Remember" token cached locally — it was never cleared.

  2. On re-login, the App automatically sends this stale "Remember" token along with the password grant request:

    { "twoFactorProvider": 5, "twoFactorToken": "<stale-cached-token>" }
    
  3. The server cannot find the token (it was deleted in Step 1) and responds with:

    { "error": "invalid_grant", "error_description": "Two factor required." }
    
  4. The App displays the "Two-step login required" screen and the user enters a valid TOTP code:

    { "twoFactorProvider": 0, "twoFactorToken": "<user-totp>" }
    
  5. Instead of clearing the stale "Remember" token and completing the login, the App immediately retries with the stale "Remember" token again — looping back to Step 2.

  6. This creates an infinite loop: stale Remember token → server rejects → TOTP prompt → user enters code → retry with stale token → server rejects → ...

The user is permanently stuck on the 2FA page and cannot log in, even though they have entered the correct TOTP code.

Screenshots or Videos

No response

Additional Context

No response

Build Version

Android 2026.4.2(21562)

What server are you connecting to?

Self-host

Self-host Server Version

No response

Environment Details

No response

Issue Tracking Info

  • I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
Originally created by @rootphantomer on GitHub (Jun 12, 2026). Original GitHub issue: https://github.com/bitwarden/android/issues/7054 ### Steps To Reproduce ### Preconditions 1. The user account has **Two-Factor Authentication (TOTP) enabled** 2. The user previously logged in on the official **Bitwarden Android App** and checked **"Remember me"** (trust this device). 3. The server is NodeWarden (self-hosted Bitwarden-compatible server). ### Steps **Step 1 — Admin revokes device trust on the server** ``` DELETE /api/devices/:deviceIdentifier ``` This cascadingly deletes the device record, all its refresh tokens, and all its 2FA "Remember" tokens. **Step 2 — Wait for access token to expire** The Android App's cached `refresh_token` has been deleted server-side. Once the `access_token` expires, the refresh attempt fails and the server logs: > `刷新登录失败:令牌不存在或已过期` > ("Refresh login failed: token does not exist or has expired") **Step 3 — User opens the Android App and attempts to re-login** 1. Enter server URL → email → master password. 2. The App **automatically includes the locally cached (but server-deleted) 2FA "Remember" token** in the request: ```json { "twoFactorProvider": 5, "twoFactorToken": "<stale-cached-token>" } ``` 3. Server looks up the token via `getTrustedTwoFactorDeviceTokenUserId(token, deviceId)` → **not found**. 4. Server returns: ```json { "error": "invalid_grant", "error_description": "Two factor required.", "TwoFactorProviders": ["0"], "TwoFactorProviders2": {"0": null} } ``` 5. App displays the **"Two-step login required"** screen. **Step 4 — User enters the TOTP code** 1. App sends: ```json { "twoFactorProvider": 0, "twoFactorToken": "<user-entered-totp>" } ``` 2. **✅ Normal case**: TOTP validates → login succeeds. 3. **❌ Bug case**: The App does **not** clear its cached stale "Remember" token, and immediately retries Step 3 (sending the expired token again) → **loops infinitely**. ### Result The user is **unable to log in** — stuck in an infinite loop on the 2FA page. ### Expected Result the App should clear the stale "Remember" token after receiving `TwoFactorRequired`, present the TOTP input, and complete login normally — without entering an infinite loop. ### Actual Result When an admin revokes device trust on the server (deleting the device's refresh tokens and 2FA "Remember" tokens), and the user subsequently tries to log in again on the official Bitwarden Android App: 1. The App still has the **stale/expired 2FA "Remember" token cached locally** — it was never cleared. 2. On re-login, the App **automatically sends this stale "Remember" token** along with the password grant request: ```json { "twoFactorProvider": 5, "twoFactorToken": "<stale-cached-token>" } ``` 3. The server cannot find the token (it was deleted in Step 1) and responds with: ```json { "error": "invalid_grant", "error_description": "Two factor required." } ``` 4. The App displays the **"Two-step login required"** screen and the user enters a valid TOTP code: ```json { "twoFactorProvider": 0, "twoFactorToken": "<user-totp>" } ``` 5. **Instead of clearing the stale "Remember" token and completing the login**, the App immediately retries with the stale "Remember" token again — looping back to Step 2. 6. This creates an **infinite loop**: stale Remember token → server rejects → TOTP prompt → user enters code → retry with stale token → server rejects → ... The user is **permanently stuck on the 2FA page and cannot log in**, even though they have entered the correct TOTP code. ### Screenshots or Videos _No response_ ### Additional Context _No response_ ### Build Version Android 2026.4.2(21562) ### What server are you connecting to? Self-host ### Self-host Server Version _No response_ ### Environment Details _No response_ ### Issue Tracking Info - [x] I understand that work is tracked outside of Github. A PR will be linked to this issue should one be opened to address it, but Bitwarden doesn't use fields like "assigned", "milestone", or "project" to track progress.
GiteaMirror added the bugapp:password-manager labels 2026-06-13 21:15:04 -05:00
Author
Owner

@bitwarden-bot commented on GitHub (Jun 12, 2026):

Thank you for your report! We've added this to our internal board for review.
ID: PM-38955

<!-- gh-comment-id:4687372351 --> @bitwarden-bot commented on GitHub (Jun 12, 2026): Thank you for your report! We've added this to our internal board for review. ID: [PM-38955](https://bitwarden.atlassian.net/browse/PM-38955) [PM-38955]: https://bitwarden.atlassian.net/browse/PM-38955?atlOrigin=eyJpIjoiNWRkNTljNzYxNjVmNDY3MDlhMDU5Y2ZhYzA5YTRkZjUiLCJwIjoiZ2l0aHViLWNvbS1KU1cifQ
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/android#127813