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 b445a38
Browse filesBrowse the repository at this point in the historyBrowse files
Dynamic Client Registration hygiene for the 2026-07-28 authorization requirements (SEP-837, SEP-2207). New `resolveClientMetadata(provider)` reads `provider.clientMetadata` and applies the spec defaults — `application_type` derived from the redirect URIs (loopback or custom scheme → `'native'`, otherwise `'web'`), `grant_types: ['authorization_code', 'refresh_token']` when omitted — and `auth()` feeds the resolved document to DCR only (scope selection still reads the raw consumer-supplied `clientMetadata` so statically-registered/CIMD clients are not pushed into `offline_access` + `prompt=consent`); consumer-set values are never overwritten. DCR rejection now throws the new `RegistrationRejectedError` carrying the HTTP status, raw body, and submitted metadata — **breaking for direct `registerClient()` callers**: rejection no longer throws `OAuthError`, so update `instanceof` checks. `OAuthClientMetadata` gains a typed `application_type?: string` field (expected `'native'` / `'web'`; tolerant on parse). `OAuthErrorCode` adds `InvalidRedirectUri`. The token-exchange, refresh, and Cross-App Access (`requestJwtAuthorizationGrant` / `exchangeJwtAuthGrant`) paths now throw the new `InsecureTokenEndpointError` for a non-`https:` token endpoint (`localhost` / `127.0.0.1` / `::1` exempt), and `auth()` surfaces it on the refresh branch instead of silently re-authorizing.
@@ -580,6 +588,10 @@ OAuth callback handling: pass the callback URL's `URLSearchParams` to `transport
580
588
581
589
`discoverAuthorizationServerMetadata()` now rejects metadata whose `issuer` does not exactly match the URL it was fetched for (RFC 8414 §3.3), throwing `IssuerMismatchError`. Pass `skipIssuerMetadataValidation: true` on `AuthOptions` (or `skipIssuerValidation: true` on the helper) only as a temporary workaround for a known-misconfigured AS.
582
590
591
+
`auth()` reads `provider.clientMetadata` once via `resolveClientMetadata()` and applies SEP-837/SEP-2207 defaults to the DCR body: `grant_types` defaults to `['authorization_code', 'refresh_token']`; `application_type` is derived from `redirect_uris` (loopback / custom URI scheme → `'native'`, otherwise `'web'`). A field you set explicitly is never overwritten — set `clientMetadata.application_type` / `clientMetadata.grant_types` to override. Direct `registerClient()` callers wanting the same defaults pass `resolveClientMetadata(provider)` as `clientMetadata`. The `grant_types` default applies to the Dynamic Client Registration body only; it does **not** drive the `offline_access` scope / `prompt=consent` augmentation on the authorize request — statically-registered and CIMD clients that want that augmentation must set `clientMetadata.grant_types` explicitly. Non-interactive providers (no `redirectUrl`) get no `grant_types` default.
592
+
593
+
Token-exchange / refresh now refuse to send credentials to a non-`https:` token endpoint (loopback `localhost` / `127.0.0.1` / `::1` exempt), throwing `InsecureTokenEndpointError` with no opt-out. `auth()` surfaces this on every path including refresh — switch any plain-`http:` AS on a non-loopback host to TLS.
594
+
583
595
No code changes required; wire-behavior note: on a 2026-07-28 Streamable HTTP connection, aborting an in-flight client request (caller `signal` / timeout) closes that request's SSE response stream as the spec cancellation signal — `notifications/cancelled` is no longer POSTed
584
596
there. 2025-era connections and stdio at any era still send `notifications/cancelled`. Custom `Transport` implementations that open one underlying request per outbound message and honor `TransportSendOptions.requestSignal` may declare `readonly hasPerRequestStream = true` to opt
Copy file name to clipboardExpand all lines: docs/migration.md
+29Lines changed: 29 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -930,6 +930,35 @@ try {
930
930
}
931
931
```
932
932
933
+
### Dynamic Client Registration: `application_type` and `grant_types` defaults (SEP-837, SEP-2207)
934
+
935
+
`OAuthClientMetadata` now has a typed `application_type?: string` field (expected `'native'` / `'web'`; tolerant on parse). `auth()` resolves your provider's `clientMetadata` once via the new `resolveClientMetadata()` and uses the resolved document for the Dynamic Client Registration body (scope selection still reads your raw `clientMetadata`). When `application_type` is unset, it is derived from your `redirect_uris`: a loopback host (`localhost` / `127.0.0.1` / `[::1]`) or a custom URI scheme yields `'native'`; anything else yields `'web'`. Set it explicitly when the heuristic is wrong for your deployment (for example a web app dev-served on `localhost`); a value you set is never overwritten.
936
+
937
+
`resolveClientMetadata()` also defaults `grant_types` to `['authorization_code', 'refresh_token']` when you omit it, so authorization servers that gate refresh-token issuance on the registered grant types issue one. If you set `grant_types` explicitly, include `'refresh_token'` yourself if you want refresh tokens. CIMD users author the hosted metadata document themselves and should include `refresh_token` there. Direct callers of `registerClient()` that want the same defaults should pass `resolveClientMetadata(provider)` as `clientMetadata`. The `grant_types` default applies to the Dynamic Client Registration body only; it does **not** drive the `offline_access` scope / `prompt=consent` augmentation on the authorize request. Statically-registered and CIMD clients that want that augmentation must set `clientMetadata.grant_types = ['authorization_code', 'refresh_token']` explicitly. Non-interactive providers (no `redirectUrl`) get no `grant_types` default.
938
+
939
+
DCR rejection now throws `RegistrationRejectedError` (carrying `status`, `body`, and `submittedMetadata`) instead of a generic `OAuthError`. Catch it to inspect the AS's `error` / `error_description` and retry with adjusted metadata, or surface a meaningful error.
const parsed =JSON.parse(e.body) as { error?:string; error_description?:string };
949
+
if (parsed.error===OAuthErrorCode.InvalidRedirectUri) {
950
+
// AS rejected redirect_uris — retry with adjusted metadata
951
+
}
952
+
}
953
+
}
954
+
```
955
+
956
+
### Token endpoint must use TLS (SEP-2207)
957
+
958
+
`exchangeAuthorization()`, `refreshAuthorization()`, `fetchToken()`, and the Cross-App Access helpers `requestJwtAuthorizationGrant()` / `exchangeJwtAuthGrant()` now throw `InsecureTokenEndpointError` when the resolved token endpoint is not `https:`. Only `localhost`, `127.0.0.1`, and `::1` are exempt for local development. `auth()` surfaces this error on every path (including the refresh branch) rather than silently re-authorizing. If you were pointing at a plain-`http:` authorization server on a non-loopback host — including cluster-DNS names like `http://oauth.svc.cluster.local` or private addresses like `http://10.0.0.5` — switch it to TLS; there is no opt-out.
959
+
960
+
**Storage confidentiality remains yours.**`OAuthClientProvider.saveTokens()` receives the raw `refresh_token`; store it in platform-appropriate secure storage. The SDK guarantees transit confidentiality but cannot secure your storage layer.
961
+
933
962
### Experimental tasks interception removed
934
963
935
964
The 2025-11 experimental tasks side-channel woven through `Protocol` has been removed in preparation for the SEP-2663 Tasks Extension. The following are gone with no in-place replacement:
0 commit comments