From f2e57eb94ff9fd061db6d8d6d48babf285d83e83 Mon Sep 17 00:00:00 2001 From: otis Date: Fri, 10 Apr 2026 17:11:31 +0000 Subject: [PATCH] Return invalid_grant on expired refresh token (#7060) --- src/api/identity.rs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/identity.rs b/src/api/identity.rs index b9a753b9..b10e1a5d 100644 --- a/src/api/identity.rs +++ b/src/api/identity.rs @@ -131,7 +131,7 @@ async fn login( login_result } -// Return Status::Unauthorized to trigger logout +// Return an OAuth2-compliant invalid_grant error to trigger client logout on refresh failure async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> JsonResult { // Extract token let refresh_token = match data.refresh_token { @@ -147,7 +147,20 @@ async fn _refresh_login(data: ConnectData, conn: &DbConn, ip: &ClientIp) -> Json // let members = Membership::find_confirmed_by_user(&user.uuid, conn).await; match auth::refresh_tokens(ip, &refresh_token, data.client_id, conn).await { Err(err) => { - err_code!(format!("Unable to refresh login credentials: {}", err.message()), Status::Unauthorized.code) + // Return an OAuth2-compliant `invalid_grant` error response so that + // Bitwarden clients recognize the expired/invalid refresh token and + // prompt the user to re-authenticate. See: #7060 + let msg = format!("Unable to refresh login credentials: {}", err.message()); + error!("{msg}"); + let result = json!({ + "error": "invalid_grant", + "error_description": msg, + "ErrorModel": { + "Message": msg, + "Object": "error" + } + }); + return Err(("invalid_grant", result).into()); } Ok((mut device, auth_tokens)) => { // Save to update `device.updated_at` to track usage and toggle new status