Skip to main content

Pre-Authorized Code

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:

  • Create a ClientApplication with Project Admin privileges.
  • Authenticate your backend as that ClientApplication, typically with the Client Credentials Flow.
  • Include the X-Medplum-On-Behalf-Of header when creating the pre-authorized code. This identifies the Medplum user who will receive the resulting token. See On-Behalf-Of for details.
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:

  • Authorization: Your ClientApplication access token or basic auth credentials
  • X-Medplum-On-Behalf-Of: The target ProjectMembership, Practitioner, Patient, or other supported profile reference
  • Content-Type: application/json

Request body:

  • clientId: Required. The target ClientApplication ID.
  • scope: Optional. Defaults to openid.
  • nonce: Optional. If omitted, Medplum generates a random nonce.
  • expiresIn: Optional expiration time in seconds. Must be between 1 and 86400.

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,
},
}
);

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:

  • grant_type: urn:ietf:params:oauth:grant-type:pre-authorized_code
  • client_id: The ClientApplication ID
  • pre-authorized_code: The one-time code returned by /auth/preauthorize

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);

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

  • Pre-authorized codes can only be redeemed once.
  • If a code is expired, already used, revoked, or presented for the wrong client, Medplum returns an OAuth error response from /oauth2/token.
  • Medplum currently supports the pre-authorized code flow without an additional transaction code (tx_code).

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