Pre-Authorized Code | Medplum

On this page

Medplum supports the OAuth 2.0 pre-authorized code grant type, urn:ietf:params:oauth:grant-type:pre-authorized_code, as defined by OpenID for Verifiable Credential Issuance 1.0. This flow is useful for magic links and other issuer-initiated flows where user authentication and consent have already happened before the client asks Medplum for tokens.

Overview

This flow has two steps:

  1. A trusted backend creates a one-time pre-authorized code by calling /auth/preauthorize.
  2. The receiving application redeems that code at /oauth2/token to obtain Medplum tokens.

Unlike the authorization code flow, this flow does not redirect the user through the authorization endpoint.

Prerequisites

To use this flow:

One-time codes

Pre-authorized codes are single-use and expire automatically. By default, Medplum issues codes that expire after 1 hour. The maximum allowed lifetime is 24 hours.

Create a pre-authorized code

Send an authenticated POST request to /auth/preauthorize.

Request headers:

Request body:

Example:

const medplum = new MedplumClient({
  baseUrl: MEDPLUM_BASE_URL,
  clientId: MY_CLIENT_ID,
  clientSecret: MY_CLIENT_SECRET,
});

const preAuthResult = await medplum.post(
  'auth/preauthorize',
  {
    clientId: MY_CLIENT_ID,
    scope: 'openid',
    expiresIn: 3600,
    nonce: 'optional-nonce-value',
  },
  undefined,
  {
    headers: {
      'X-Medplum-On-Behalf-Of': ON_BEHALF_OF,
    },
  }
);
curl -X POST 'https://api.medplum.com/auth/preauthorize' \
  --user "$MY_CLIENT_ID:$MY_CLIENT_SECRET" \
  -H 'Content-Type: application/json' \
  -H 'X-Medplum-On-Behalf-Of: Practitioner/00000000-0000-0000-0000-000000000000' \
  --data-raw '{
    "clientId": "00000000-0000-0000-0000-000000000000",
    "scope": "openid",
    "expiresIn": 3600
  }'

On success, Medplum returns a response like:

{
  "preAuthorizedCode": "<ONE_TIME_CODE>",
  "expiresAt": "2026-05-16T20:15:00.000Z"
}

You can package this code into a magic link, QR code, or handoff to a wallet or application that will redeem it.

Redeem the pre-authorized code

To redeem the code, send a form-encoded POST request to /oauth2/token with:

Example:

const tokenResponse = await fetch(`${MEDPLUM_BASE_URL}oauth2/token`, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/x-www-form-urlencoded',
  },
  body: new URLSearchParams({
    grant_type: 'urn:ietf:params:oauth:grant-type:pre-authorized_code',
    client_id: MY_CLIENT_ID,
    'pre-authorized_code': preAuthResult.preAuthorizedCode,
  }),
});

const tokens = await tokenResponse.json();

console.log(tokens.access_token);
curl -X POST 'https://api.medplum.com/oauth2/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  --data-urlencode 'grant_type=urn:ietf:params:oauth:grant-type:pre-authorized_code' \
  --data-urlencode 'client_id=00000000-0000-0000-0000-000000000000' \
  --data-urlencode 'pre-authorized_code=<PRE_AUTHORIZED_CODE>'

On success, the response includes a Medplum access token and ID token:

{
  "token_type": "Bearer",
  "access_token": "<ACCESS_TOKEN>",
  "id_token": "<ID_TOKEN>",
  "scope": "openid",
  "expires_in": 3600
}

Notes

For more details, see the OpenID for Verifiable Credential Issuance 1.0 specification, especially the sections on Pre-Authorized Code Flow and Token Request.