Well, kind of an update: this was also discussed here last month – and there is a related PR on GitHub, that you could also follow (“subscribe” to get notifications):
main ← platform/handle-401-refresh-token
opened 09:01PM - 15 Nov 25 UTC
## 🎟️ Tracking
https://bitwarden.atlassian.net/browse/PM-28303
fixes https… ://github.com/bitwarden/clients/issues/10718
## 📔 Objective
Attempt to refresh the access token if we receive a `401` response from an API request.
This matches the behavior on the mobile applications, and accounts for scenarios in which we've seen requests being delayed client-side (seemingly related to host sleep/hibernate states), only to proceed after the machine is active, at which point the now expired access token fails.
This change is meant to specifically address the case where:
1. We make an authenticated API request with an access token that is valid (not expired) at the time that our code calls `fetch()`.
2. For some reason, the request is not sent to the server until _after_ the access token has expired.
3. The Bitwarden API rejects the request with a `401` response because the token is not valid.
Specifically in this scenario, if we run the request building logic again, **the access token will now be detected as expired** and a `refresh_token` request will be issued to Identity. This refreshed access token can then be used to successfully send the request.
Besides renames and using `HttpStatusCode`s instead of magic strings for HTTP response statuses, the following substantial changes were made:
1. Added check for a `401` response on an authenticated request, and call `buildRequest()` again to refresh the token.
2. Changed the logout reasons to `SessionExpired` for an invalid refresh token (meaning that the refresh token (aka a "session" is no longer valid) and `InvalidAccessToken` for an API request that receives a `401` or `403` response. PR https://github.com/bitwarden/clients/pull/17608 builds on this by adding a log message on every logout with a reason.
3. Alphabetized the `LogoutReason` type and removed the `invalidGrant` reason, since it was no longer in use.
4. Separated out the response error handling into 2 separate methods for clarity. One handles responses from API requests, and one handles errors from requests to the token endpoint. Why don't we need to check for `401` or `403` on the token request failures? Because we're not calling an authenticated endpoint here, we're calling the token request endpoint, and that endpoint will respond with an [invalid grant](https://github.com/DuendeSoftware/products/blob/508e82a1f2e06ef9ff67d0453b387e8ae71692f3/identity-server/src/IdentityServer/Services/Default/DefaultRefreshTokenService.cs#L79-L80).
⚠️ This does **not** handle the following scenarios:
1. A `401` response is received because the wrong user's access token is sent on a request.
4. A `401` response is received due to network configuration issues.
In these case, the token isn't expired, and we'll send up the same (wrong) token again. At that point, we'll get a `401` again and log the user out as we did before. We'll have an extra request in this case, but it will fail both times.
## 📸 Screenshots
This demo demonstrates what happens in the scenario that this change covers, by doing the following:
1. Modifying the access token lifetime to be 10 seconds
2. Setting a breakpoint after the access token was acquired (when building the request) and **before** the ``fetch()` is executed.
3. Waiting at that breakpoint for more than 10 seconds. At this point, the token has expired.
4. Sending the request with the expired token.
5. Observing that the response is `401 - Unauthorized`
6. Letting the code execute to see that a subsequent refresh occurs, and the request then succeeds.
https://github.com/user-attachments/assets/a5250c87-29f0-4056-b63e-65001aebe675
## ⏰ Reminders before review
- Contributor guidelines followed
- All formatters and local linters executed and passed
- Written new unit and / or integration tests where applicable
- Protected functional changes with optionality (feature flags)
- Used internationalization (i18n) for all UI strings
- CI builds passed
- Communicated to DevOps any deployment requirements
- Updated any necessary documentation (Confluence, contributing docs) or informed the documentation team
## 🦮 Reviewer guidelines
- 👍 (`:+1:`) or similar for great changes
- 📝 (`:memo:`) or ℹ️ (`:information_source:`) for notes or general info
- ❓ (`:question:`) for questions
- 🤔 (`:thinking:`) or 💭 (`:thought_balloon:`) for more open inquiry that's not quite a confirmed issue and could potentially benefit from discussion
- 🎨 (`:art:`) for suggestions / improvements
- ❌ (`:x:`) or ⚠️ (`:warning:`) for more significant problems or concerns needing attention
- 🌱 (`:seedling:`) or ♻️ (`:recycle:`) for future improvements or indications of technical debt
- ⛏ (`:pick:`) for minor or nitpick changes
I think you meant Bitwarden ?
Yep. Please. (also because I’m not sure what exactly the issue is in that paragraph…)
Same to you!