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
{{ message }}
Repository navigation
Commit c69cc52
Browse filesBrowse the repository at this point in the historyBrowse files
Contain the provider boundary on the delegated refresh grant
A credential provider is an external plugin boundary, so nothing it authors
may reach host error channels, telemetry, or persisted connection state —
those values can carry token responses and other secret material.
- Close the rejection classification to the standards-defined set (RFC 6749
§5.2 plus RFC 8707 `invalid_target`) and validate it at runtime, so an
unrecognised value cannot reach `oauthErrorCode`, span attributes or
persisted health. Drop `message`/`cause` from `RefreshGrantRejected`
entirely; Executor now emits fixed host-facing text carrying only the
validated code.
- Contain provider storage failures, synchronous throws, Effect defects, and
throwing or stateful property getters — including on the capability itself,
on the success object's fields, and on the post-grant read-back.
Cancellation is still propagated as cancellation; only the
provider-authored reasons are dropped.
- Rebuild the persisted scope from the host's own recorded grant set rather
than the provider's string. `oauth_scope` is replayed to the authorization
server on the next refresh, so accepting it verbatim was a persisted
provider-controlled channel. A scope outside the granted set fails the
refresh (RFC 6749 §6: a refresh may narrow scope, never widen it).
- Bound the reported lifetime to finite, non-negative and at most ten years,
instead of stamping NaN/Infinity/negative straight into `expires_at`.
- Treat an unresolvable read-back as a retryable provider-invariant failure
rather than demanding re-authentication: the authorization server ACCEPTED
the grant, so re-auth is the one remedy that cannot be required, and a
rotated refresh token may already be sealed.
- Re-export the contract from the promise and shared surfaces too, and
generalise the security note from `tokenUrl` to the whole caller-authored
input tuple.
Copy file name to clipboardExpand all lines: .changeset/provider-owned-oauth-refresh-grant.md
+2-2Lines changed: 2 additions & 2 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,6 @@
6
6
7
7
`CredentialProvider` gains an optional `refreshGrant`. When a provider implements it, the host asks it to _perform_ the refresh exchange rather than to hand over the refresh token: the provider spends the token, seals the newly minted access token (and a rotated refresh token, if the authorization server sent one) under the same item ids, and returns only the granted lifetime and scope. The host then resolves the access token through `get`, the same hop every other credential takes.
8
8
9
-
This closes the one gap where a backend that keeps secrets sealed had no honest option but to refuse to refresh at all — the refresh grant is the only exchange where a long-lived stored secret must be spent and the reply is itself a fresh credential. Providers that do not implement `refreshGrant` are unaffected: the existing host-side exchange runs unchanged.
9
+
This gives a backend that keeps secrets sealed a way to close the refresh gap instead of refusing refresh entirely — the refresh grant is the exchange where a long-lived stored secret must be spent and the reply is itself a fresh credential. The provider remains responsible for authenticating the complete caller-supplied grant tuple against independently trusted enrollment metadata before opening a secret. Providers that do not implement `refreshGrant` are unaffected: the existing host-side exchange runs unchanged.
10
10
11
-
A refused grant is reported with the new `RefreshGrantRejected` error carrying the RFC 6749 §5.2 code, so a delegated refresh classifies re-authentication, surfaces `invalid_grant` to the caller, and arms the known-dead gate exactly as the host-side path does.
11
+
A refused grant is reported with the new `RefreshGrantRejected` error carrying a closed standards-defined token-endpoint code (RFC 6749 §5.2 plus RFC 8707 `invalid_target`), so a delegated refresh classifies re-authentication, surfaces `invalid_grant` to the caller, and arms the known-dead gate exactly as the host-side path does. Free-form provider messages, causes, defects, and malformed result metadata stay inside the provider boundary; Executor generates fixed host-facing text and only persists validated lifetime/scope metadata.
0 commit comments