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:
- A trusted backend creates a one-time pre-authorized code by calling
/auth/preauthorize. - The receiving application redeems that code at
/oauth2/tokento 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
ClientApplicationwith Project Admin privileges. - Authenticate your backend as that
ClientApplication, typically with the Client Credentials Flow. - Include the
X-Medplum-On-Behalf-Ofheader when creating the pre-authorized code. This identifies the Medplum user who will receive the resulting token. See On-Behalf-Of for details.
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: YourClientApplicationaccess token or basic auth credentialsX-Medplum-On-Behalf-Of: The targetProjectMembership,Practitioner,Patient, or other supported profile referenceContent-Type: application/json
Request body:
clientId: Required. The targetClientApplicationID.scope: Optional. Defaults toopenid.nonce: Optional. If omitted, Medplum generates a random nonce.expiresIn: Optional expiration time in seconds. Must be between1and86400.
Example:
- TypeScript
- cURL
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:
grant_type:urn:ietf:params:oauth:grant-type:pre-authorized_codeclient_id: TheClientApplicationIDpre-authorized_code: The one-time code returned by/auth/preauthorize
Example:
- TypeScript
- cURL
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
- 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.