diff --git a/docs/index.md b/docs/index.md index be3ba7da9..c55caba9d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -23,6 +23,7 @@ To understand how to use this library see here: - [Resource Owner Password Credentials (ROPC) Grant](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/resource-owner-password-credentials-grant.md) - [Refresh Token Grant](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/refresh-token-grant.md) - [Silent Refresh Token in iframe Flow](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/silent-refresh-token-in-iframe-flow.md) +- [Demonstrating Proof of Possession](https://github.com/authts/oidc-client-ts/blob/main/docs/protocols/demonstrating-proof-of-possession.md) # UserManager @@ -134,8 +135,8 @@ The `url_state` will be appended to the opaque, unique value created by the libr # Hash-mode router (SPA) -If your app is using hash-based routing, be aware that many OIDC providers append the query string after the hash instead of inserting it before: -**Correct:** `https://your.org/?code=ab&state=cd#/oidc-callback` +If your app is using hash-based routing, be aware that many OIDC providers append the query string after the hash instead of inserting it before: +**Correct:** `https://your.org/?code=ab&state=cd#/oidc-callback` **Wrong:** `https://your.org/#/oidc-callback?code=ab&state=cd` Check out [this issue]([https://github.com/authts/oidc-client-ts/issues/734#issuecomment-1298381823](https://github.com/authts/oidc-client-ts/issues/734)) for details. (There are also workarounds, as long as your provider doesn't fix the issue) diff --git a/docs/protocols/demonstrating-proof-of-possession.md b/docs/protocols/demonstrating-proof-of-possession.md new file mode 100644 index 000000000..4441f57fb --- /dev/null +++ b/docs/protocols/demonstrating-proof-of-possession.md @@ -0,0 +1,73 @@ +# Demonstrating Proof of Possession (DPoP) + +Demonstrating Proof of Possession (DPoP) is defined in [RFC 9449 OAuth2.0 Demonstrating Proof of Possession (DPoP)](https://datatracker.ietf.org/doc/html/rfc9449). + +DPoP is described as a mechanism for sender-constraining OAuth 2.0 tokens via a proof-of-possession mechanism on the application level. This mechanism allows for the detection of replay attacks with access and refresh tokens. + +Essentially this means that tokens are bound to a particular client device, as long as the private key used to generate +the DPoP proof is not compromised. + +## Usage + +To use the DPoP feature add the `dpop` configuration option when instantiating either the UserManager or OidcClient classes: + +```typescript +import { UserManager } from 'oidc-client-ts'; + +const settings = { + authority: 'https://demo.identityserver.io', + client_id: 'interactive.public', + redirect_uri: 'http://localhost:8080', + response_type: 'code', + scope: 'openid profile email api', + post_logout_redirect_uri: 'http://localhost:8080', + userStore: new WebStorageStateStore({ store: window.localStorage }), + dpop: { + bind_authorization_code: true, + store: new IndexedDbDPoPStore() + } +}; + +const userManager = new UserManager(settings); +``` +## DPoP configuration options + +- `bind_authorization_code` - If true, the DPoP proof will be [bound to the authorization code](https://datatracker.ietf.org/doc/html/rfc9449#name-authorization-code-binding-) as well as subsequent token requests. This is optional and defaults to false. +- `store` - The DPoP store to use. This is where the DPoP proof will be stored and must be supplied. We provide a default implementation `IndexedDbDPoPStore` which stores the DPoP proof in IndexedDb. + +### IndexedDbDPoPStore + +The `IndexedDbDPoPStore` is a default implementation of the `DPoPStore` interface. It stores the DPoP proof in IndexedDb. + +IndexedDb is used as storage because it is the only storage mechanism that is available that allows for storing CryptoKeyPair objects +with non-extractable private keys securely. The object itself, when retrieved from storage, is still available to perform signing operations but the key material can +never be extracted directly. Storing CryptoKeyPair objects as plain text in storage mechanisms such as `localStorage` or `sessionStorage` +is not recommended as it exposes the private key material to potential attackers. + +## DPoP Proofs and Protected Resources + +Once an access token has been issued that is bound to a DPoP public key, [resource servers](https://datatracker.ietf.org/doc/html/rfc9449#name-protected-resource-access) that support DPoP +require that an authenticated request include a DPoP Proof. Additionally, DPoP replaces the Bearer authentication [scheme](https://datatracker.ietf.org/doc/html/rfc9449#section-7.1): + +The `UserManager.dpopProof` method is provided which allows easily generating a DPoP proof for a given request. E.g. + +```typescript +// settings include the DPoP configuration + +const mgr = new UserManager(settings); + +// sign the user in and receive an access token + +// generate a DPoP proof and make a request to a protected resource +const DPoPProof = await mgr.dpopProof("https://localhost:5005/api", user); +const token = user?.access_token; +const response = await fetch("https://localhost:5005/api", { + credentials: "include", + mode: "cors", + method: "GET", + headers: { + Authorization: `DPoP ${token}`, + DPoP: DPoPProof, + }, +}); +```