Update ADR for OIDC token refresh
This commit is contained in:
@@ -4,9 +4,9 @@ Date: 2023-09-11
|
||||
|
||||
## Status
|
||||
|
||||
- Issue:
|
||||
Superseded by [ADR-0035](0035-error-handling-on-no-longer-valid-oidc-token.md)
|
||||
|
||||
[1974](https://github.com/linagora/tmail-flutter/issues/1974)
|
||||
- Issue: [1974](https://github.com/linagora/tmail-flutter/issues/1974)
|
||||
|
||||
## Context
|
||||
|
||||
|
||||
@@ -1,19 +1,37 @@
|
||||
# 35. Error handling on no longer valid OIDC token (Issue #2592)
|
||||
# 35. OIDC Token Refresh Mechanism in AuthorizationInterceptors
|
||||
|
||||
Date: 2024-02-15
|
||||
Updated: 2026-02-09
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
Accepted (supersedes [ADR-0031](0031-fix-refresh-token-with-oidc.md))
|
||||
|
||||
## Context
|
||||
|
||||
- When my OIDC token is expired, I see an awful red error message while being redirected
|
||||
Multiple issues drove the evolution of the refresh token logic:
|
||||
|
||||
1. **Concurrent 401s caused logout** ([#1974](https://github.com/linagora/tmail-flutter/issues/1974)) — parallel requests with stale headers all triggered independent refresh attempts.
|
||||
2. **Ugly error on expired token** ([#2592](https://github.com/linagora/tmail-flutter/issues/2592)) — users saw a red error instead of a silent redirect.
|
||||
3. **400 on refresh not handled** — when the refresh token itself was revoked/expired, the server returned 400 (invalid_grant) but the interceptor did not distinguish this from transient errors, leading to confusing failures.
|
||||
4. **Server-side revocation before local expiry** — relying on local `isExpired` check caused the interceptor to skip refresh when the server had already revoked the token.
|
||||
|
||||
## Decision
|
||||
|
||||
- When my OIDC token is expired, I am just redirected to the OIDC provider portal. No need to show an awful error.
|
||||
`AuthorizationInterceptors` extends `QueuedInterceptorsWrapper` (Dio). The `onError` handler processes errors one at a time with the following ordered checks:
|
||||
|
||||
1. **`_refreshAttemptedKey` guard** — if the request already attempted a refresh/retry, skip everything and propagate the error. Prevents infinite loops.
|
||||
2. **`validateToRetryTheRequestWithNewToken`** (checked first) — only on **401** responses: if `_token` was already updated by a preceding queued request, retry immediately with the new token. No refresh call needed. Non-401 errors (500, 403, etc.) are never retried here — they are server errors unrelated to authentication.
|
||||
3. **`validateToRefreshToken`** — if status is 401, auth type is OIDC, and access/refresh tokens are present, attempt refresh. The local `isExpired` check was **removed**; we trust the server's 401.
|
||||
- **Success (different token):** update `_token`, persist to cache, mark `_refreshAttemptedKey`, retry.
|
||||
- **Success (same token — duplicate):** propagate original error, no retry.
|
||||
- **`DioException` 400:** call `clear()` (wipe auth state) and reject with `RefreshTokenFailedException` — triggers silent logout.
|
||||
- **`DioException` other:** propagate original error, preserve OIDC state.
|
||||
4. **Outer catch:** `ServerError`/`TemporarilyUnavailable` wrapped in `DioException`; other exceptions forwarded via `err.copyWith`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- In case of receiving a `BadCredentials` error, the system automatically logs out and returns to the login screen without any notification to the user.
|
||||
- Queued requests after a successful refresh retry without redundant refresh calls.
|
||||
- 400 from the OIDC provider causes immediate session cleanup and silent redirect to login.
|
||||
- No infinite retry loops thanks to the `_refreshAttemptedKey` per-request flag.
|
||||
- Server-side token revocation is handled correctly regardless of local expiry time.
|
||||
|
||||
Reference in New Issue
Block a user