---
title: 'Reference'
---
# @muze-nl/metro-oauth2 reference

```js
import oauth2, {
  oauth2mw,
  dpopmw,
  discover,
  authorizePopup,
  popupHandleRedirect,
  tokenStore,
  keysStore,
  isRedirected,
  isAuthorized,
  parseBearerChallenge
} from '@muze-nl/metro-oauth2'
```

## `oauth2mw(options)`

```js
const api = client('https://resource.example/')
  .with(oauth2mw({
    site: 'https://issuer.example/',
    oauth2_configuration: {
      client_id: 'my-client-id',
      token_endpoint_auth_method: 'none',
      grant_type: 'authorization_code',
      response_type: 'code',
      authorization_endpoint: 'https://issuer.example/authorize',
      token_endpoint: 'https://issuer.example/token',
      redirect_uri: location.href,
      scope: 'profile'
    }
  }))
```

Adds OAuth2 authorization to a Metro client. By default it first tries the request, then starts authorization if the response is `401` or `403`. Set `force_authorization: true` to authorize before the request.

Important options: `site`, `client`, `authorize_callback`, `force_authorization`, `state`, `tokens`, and `oauth2_configuration`.

`oauth2_configuration` may include `access_token`, `authorization_code`, `authorization_endpoint`, `client_id`, `client_secret`, `code_verifier`, `grant_type`, `login_hint`, `prompt`, `redirect_uri`, `refresh_token`, `scope`, `token_endpoint`, and `token_endpoint_auth_method`. `login_hint` and `prompt` are passed on to the authorization endpoint. Set `issuer` to check the `iss` parameter of authorization responses (RFC 9207): a response from another issuer is refused, and with `authorization_response_iss_parameter_supported: true` a response without `iss` is refused too. `oidcmw()` sets both from the issuer's discovery metadata. Set `token_type` (e.g. `'DPoP'`) to require that token type: a token response with another type, such as a Bearer token where a DPoP-bound token was requested, is refused and not stored.

`authorize_callback(url)` receives the authorization URL and returns the authorization code. The default callback redirects the browser there and returns `false`. When the callback returns no code, because it declined or is redirecting, the request rejects with an error whose `code` is `'authorization_not_completed'`, and the request is not sent. It never resolves as a successful response.

Requests that share stored tokens (the same `tokens` object, or the same `site` with the default store) also share token requests. When several requests need a new access token at the same moment, one refresh or authorization runs and the others wait for its result.

Do not put a real client secret in browser code.

## PKCE helpers

```js
const verifier = oauth2.generateCodeVerifier()
const challenge = await oauth2.generateCodeChallenge(verifier)
```

Also exported: `base64url_encode(buffer)`, `createState(length)`, `getExpires(duration)`, and `isExpired(token)`.

## Token and state checks

```js
if (isRedirected()) {
  // The current URL looks like an OAuth2 redirect callback.
}

if (isAuthorized(tokens)) {
  // Stored tokens contain a usable access token.
}
```

`parseBearerChallenge(value)` parses `WWW-Authenticate` Bearer/DPoP challenges.

<a name="popup-flow"></a>
## Popup flow

```js
const api = client('https://resource.example/')
  .with(oauth2mw({
    authorize_callback: authorizePopup,
    oauth2_configuration: {
      client_id: 'my-client-id',
      token_endpoint_auth_method: 'none',
      authorization_endpoint: 'https://issuer.example/authorize',
      token_endpoint: 'https://issuer.example/token',
      redirect_uri: `${location.origin}/oauth2-callback.html`
    }
  }))
```

`authorizePopup` resolves with the authorization response `{ authorization_code, state, iss }`, which `oauth2mw` checks. It rejects with `'OAuth2 popup was blocked'` when the popup cannot open, and with `'OAuth2 popup was closed'` when the user closes it before logging in.

Callback page:

```html
<script type="module">
  import { popupHandleRedirect } from '@muze-nl/metro-oauth2'
  popupHandleRedirect()
  window.close()
</script>
```

## `dpopmw(options)`

```js
const api = client('https://resource.example/')
  .with(dpopmw({
    site: 'https://issuer.example/',
    token_endpoint: 'https://issuer.example/token'
  }))
```

Adds DPoP proofs (RFC 9449) to token endpoint requests, so the issuer binds the tokens to this client's key, and to requests whose `Authorization` header uses the `DPoP` scheme. Other requests are sent unchanged. When the token endpoint (`400`) or a resource (`401`) answers with `use_dpop_nonce`, the request is sent once more with a proof that includes the server's `DPoP-Nonce`; later proofs for that server include its latest nonce. OIDC uses this by default because Solid identity and storage servers commonly require sender-constrained tokens.

## Stores

```js
const tokens = tokenStore('https://issuer.example/')
const keys = keysStore()
```

The default stores use `localStorage` when available and fall back to memory where implemented.

## Discovery

```js
const oauthClient = discover({ issuer: 'https://issuer.example/' })
```

Creates a discovery-capable helper for OAuth authorization server metadata.

## Test mock server

```js
import oauth2mockserver from '@muze-nl/metro-oauth2/testing'
```

The mock server is for tests and examples, not production browser bundles.

Authorization codes are retired before sending their token exchange request.
A failed, lost, or invalid token response is surfaced to the caller; the next
attempt requests a fresh authorization instead of reusing a possibly spent code.
This follows the single-use requirement in
[RFC 6749 section 4.1.2](https://www.rfc-editor.org/rfc/rfc6749#section-4.1.2).
