[PR #7080] [CLOSED] Return invalid_grant on expired refresh token (#7060) #26092

Closed
opened 2026-05-29 05:05:13 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/dani-garcia/vaultwarden/pull/7080
Author: @pmhnam
Created: 4/10/2026
Status: Closed

Base: mainHead: fix/refresh-token-invalid-grant-7060


📝 Commits (1)

  • f2e57eb Return invalid_grant on expired refresh token (#7060)

📊 Changes

1 file changed (+15 additions, -2 deletions)

View changed files

📝 src/api/identity.rs (+15 -2)

📄 Description

Summary

Fixes #7060

When a client's refresh_token expires, the server returns a response that Bitwarden clients don't recognize as a session expiration, causing them to silently fail instead of prompting the user to re-authenticate.

Root Cause

Bitwarden clients detect expired sessions by checking the token refresh response for:

  • HTTP status 400 Bad Request
  • JSON body with "error": "invalid_grant"

(Reference: api.service.ts#L1799-L1810)

if (response.status === HttpStatusCode.BadRequest && responseJson?.error === "invalid_grant") {
    await this.logoutCallback("sessionExpired");
}

However, Vaultwarden was returning 401 Unauthorized with a standard error body where "error" is an empty string — none of the client's checks matched, so the client never triggered re-authentication.

Changes

src/api/identity.rs_refresh_login():

Changed the error response for failed refresh token exchanges to return an OAuth2-compliant invalid_grant error:

Before After
HTTP Status 401 Unauthorized 400 Bad Request
error field "" (empty) "invalid_grant"
error_description "" (empty) Descriptive message

This follows the same pattern already used for 2FA errors in the codebase (see _json_err_twofactor), and aligns with RFC 6749 §5.2 which specifies invalid_grant for expired/revoked tokens.

Testing

  1. Log in on a Bitwarden client (desktop or browser extension)
  2. Wait for the refresh token to expire (or force expiration)
  3. Trigger a sync or vault edit on the client
  4. Before fix: Client shows cryptic errors, never prompts re-login
  5. After fix: Client detects invalid_grant, triggers sessionExpired logout callback, and prompts user to re-authenticate

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/dani-garcia/vaultwarden/pull/7080 **Author:** [@pmhnam](https://github.com/pmhnam) **Created:** 4/10/2026 **Status:** ❌ Closed **Base:** `main` ← **Head:** `fix/refresh-token-invalid-grant-7060` --- ### 📝 Commits (1) - [`f2e57eb`](https://github.com/dani-garcia/vaultwarden/commit/f2e57eb94ff9fd061db6d8d6d48babf285d83e83) Return invalid_grant on expired refresh token (#7060) ### 📊 Changes **1 file changed** (+15 additions, -2 deletions) <details> <summary>View changed files</summary> 📝 `src/api/identity.rs` (+15 -2) </details> ### 📄 Description # Summary Fixes #7060 When a client's `refresh_token` expires, the server returns a response that Bitwarden clients don't recognize as a session expiration, causing them to silently fail instead of prompting the user to re-authenticate. # Root Cause Bitwarden clients detect expired sessions by checking the token refresh response for: - HTTP status `400 Bad Request` - JSON body with `"error": "invalid_grant"` ([Reference: api.service.ts#L1799-L1810](https://github.com/bitwarden/clients/blob/69f9c7d30dd4e013101667ac7f79f0e68e6bd122/libs/common/src/services/api.service.ts#L1799-L1810)) ```typescript if (response.status === HttpStatusCode.BadRequest && responseJson?.error === "invalid_grant") { await this.logoutCallback("sessionExpired"); } ``` However, Vaultwarden was returning `401 Unauthorized` with a standard error body where `"error"` is an empty string — none of the client's checks matched, so the client never triggered re-authentication. # Changes **`src/api/identity.rs`** — `_refresh_login()`: Changed the error response for failed refresh token exchanges to return an OAuth2-compliant `invalid_grant` error: | | Before | After | |---|---|---| | HTTP Status | `401 Unauthorized` | `400 Bad Request` | | `error` field | `""` (empty) | `"invalid_grant"` | | `error_description` | `""` (empty) | Descriptive message | This follows the same pattern already used for 2FA errors in the codebase (see `_json_err_twofactor`), and aligns with [RFC 6749 §5.2](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2) which specifies `invalid_grant` for expired/revoked tokens. # Testing 1. Log in on a Bitwarden client (desktop or browser extension) 2. Wait for the refresh token to expire (or force expiration) 3. Trigger a sync or vault edit on the client 4. **Before fix:** Client shows cryptic errors, never prompts re-login 5. **After fix:** Client detects `invalid_grant`, triggers `sessionExpired` logout callback, and prompts user to re-authenticate --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-05-29 05:05:14 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/vaultwarden#26092