Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/clients/middleware.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ const authed = new StreamableHTTPClientTransport(serverUrl, {

`provider` is the same `OAuthClientProvider` you would hand to the transport directly. With `statusLevel: 400`, `withLogging` stays silent until a request fails.

When the provider implements `dpop()`, `withOAuth` already composes DPoP request signing and nonce handling for DPoP-bound access tokens. Do not wrap the same `fetch` again with `withDpop`. See [Enable DPoP](./oauth.md#enable-dpop) for the provider setup; use `withDpop` directly only when you manage the bound token and signing session yourself.

::: tip
For the common case, pass `authProvider` to the transport instead — see [OAuth](./oauth.md). `withOAuth` is for stacks that already own `fetch` and need auth composed with other layers.
:::
Expand Down
22 changes: 22 additions & 0 deletions docs/clients/oauth.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,28 @@ class MyOAuthProvider implements OAuthClientProvider {

The SDK calls the `save*` methods as the flow produces values and reads them back through `tokens()`, `clientInformation()`, `codeVerifier()`, and `discoveryState()`. On a later `connect()` it reads `tokens()` before anything else, so a provider backed by durable storage skips the browser round trip.

## Enable DPoP

DPoP (Demonstrating Proof of Possession) binds an OAuth access token to a signing key held by the client. To opt in, return a `DpopSession` from your provider's `dpop()` method:

```ts source="../../examples/guides/clients/oauth.examples.ts#DpopOAuthProvider_class"
class DpopOAuthProvider extends MyOAuthProvider {
private readonly dpopSession = DpopSession.create();

dpop() {
return this.dpopSession;
}
}
```

Use `new DpopOAuthProvider()` in place of `new MyOAuthProvider()` in the connection example, and keep that provider for the callback and reconnect. `DpopSession.create()` returns a promise; storing it once keeps the same signing key and nonce state for the lifetime of the client registration.

The SDK signs token requests with DPoP proofs. When the authorization server issues `token_type: "DPoP"`, the transports present the access token with the `DPoP` authorization scheme and a fresh proof for each request. A Bearer token still uses the Bearer scheme. Neither the transports nor `withOAuth` need an additional `withDpop` wrapper; see [Client middleware](./middleware.md#combine-middleware-with-an-auth-provider).

::: warning Persist the signing key with the tokens
This example keeps both tokens and the DPoP session in memory. If your application persists DPoP-bound tokens across restarts, preserve their signing key in secure storage too, or discard those tokens and authorize again. A new `DpopSession.create()` generates a new key; it cannot prove possession of the key that existing tokens are bound to. The default private key is non-extractable, so persisting only the tokens is not sufficient.
:::

## Finish the flow from the callback

The authorization server redirects the end user to `redirectUrl` with `code` and `state` in the query. Compare `state`, hand the whole query to `finishAuth`, and reconnect.
Expand Down
15 changes: 15 additions & 0 deletions examples/guides/clients/oauth.examples.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type {
import {
checkResourceAllowed,
Client,
DpopSession,
IssuerMismatchError,
resourceUrlFromServerUrl,
StreamableHTTPClientTransport,
Expand Down Expand Up @@ -98,6 +99,20 @@ class MyOAuthProvider implements OAuthClientProvider {
}
//#endregion MyOAuthProvider_class

// ---------------------------------------------------------------------------
// "Enable DPoP"
// ---------------------------------------------------------------------------

//#region DpopOAuthProvider_class
class DpopOAuthProvider extends MyOAuthProvider {
private readonly dpopSession = DpopSession.create();

dpop() {
return this.dpopSession;
}
}
//#endregion DpopOAuthProvider_class

// ---------------------------------------------------------------------------
// "Hand the transport an OAuth provider"
// ---------------------------------------------------------------------------
Expand Down
Loading