You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
OAuth client: refresh before re-authorizing, and discover before refreshing
OAuthClientProvider had two disconnected ways to obtain a token: a
pre-request branch that could refresh but never discovered metadata, and
a 401 branch that discovered but never tried the refresh token. After a
process restart the first is unreachable (no expiry is restored, so the
loaded token always reads as valid) and the second goes straight to
interactive authorization, so every restarted client re-opened the
browser once its access token lapsed and headless clients failed
outright. When an application forced the pre-request path, the refresh
was posted to a path guessed from the server origin and 404ed against
any authorization server mounted under a path. Separately, a dynamically
registered client whose secret had expired was reused forever: the
stored client_secret_expires_at was never read and invalid_client from
the token endpoint was not recognised, so each attempt spent a consent
and failed at the code exchange.
Both entry points now drive one sequence, _reacquire_tokens: discover
(always on a 401, and before a cold-start refresh when nothing is
cached), drop a registration the SDK minted whose secret has lapsed,
try the refresh_token grant when one is held, and only then run the
provider's full grant. invalid_client from the token endpoint discards
an SDK-minted registration and its tokens and runs registration once
more; pre-registered credentials surface the error instead. _initialize
derives an expiry from the loaded token unless the application already
set one. The 2025-03-26 origin-path fallbacks remain for servers with no
metadata but are no longer reached merely because state was not loaded.
Closes#3240, #3250, #3256, #1318.
Copy file name to clipboardExpand all lines: docs/client/oauth-clients.md
+6-1Lines changed: 6 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -51,6 +51,11 @@ The in-memory version above works. It also forgets everything when the process e
51
51
!!! tip
52
52
Store `client_info`, not only the tokens. The provider registers dynamically the first time it
53
53
finds no stored `client_info`. Throw it away and you mint a fresh registration on every run.
54
+
The one case where the provider throws it away for you is a registration it made that has
55
+
stopped working: its `client_secret_expires_at` has passed, or the token endpoint answered
56
+
`invalid_client`. Then it registers again and overwrites the stored record. Credentials you
57
+
seeded into storage yourself are never replaced this way; an `invalid_client` for those
58
+
surfaces as an `OAuthTokenError`.
54
59
55
60
### The two handlers
56
61
@@ -81,7 +86,7 @@ The first time `Client` sends a request, the server answers `401`. The provider
81
86
3.**Authorization.** It generates the PKCE pair and a `state`, builds the authorization URL, awaits your `redirect_handler`, then awaits your `callback_handler` for the code.
82
87
4.**Exchange.** It trades the code for an `OAuthToken`, stores it, and replays your original request with `Authorization: Bearer ...`.
83
88
84
-
After that it is quiet. Tokens come out of storage, an expired access token is refreshed with the refresh token, and only when none of that works does it run the flow again.
89
+
After that it is quiet. Tokens come out of storage, an expired access token is refreshed with the refresh token, and only when none of that works does it run the flow again. That holds across restarts: a new process that finds a refresh token in storage answers the first `401` by rediscovering the authorization server and refreshing, not by sending anyone back to the browser.
85
90
86
91
You wrote none of it. Two keyword arguments remain (`client_metadata_url` and `validate_resource_url`), and this file needs neither. `client_metadata_url` is the one worth knowing about; it gets its own section below.
0 commit comments