loading
Preparing LoginRadius developer resources
Mission: Help enterprises accelerate digital transformation with our fully-managed Customer IAM technology.
Skip to main content

Pushed Authorization Request (PAR) & Step-up Authentication

Pushed Authorization Request (PAR) is defined in RFC 9126. It allows a client to push its authorization parameters directly to the authorization server — before redirecting the user — receiving back a short-lived request_uri reference. This eliminates sensitive parameters from the browser URL and enables richer payloads such as authorization_details (RFC 9396) to be transmitted securely over a back-channel.

LoginRadius extends PAR to support Step-up Customer Authentication (SCA): a pattern where an already-authenticated user must re-verify their identity before completing a sensitive operation (for example, a high-value payment or account change).


Key Concepts

ConceptDescription
request_uriOpaque reference (urn:ietf:params:oauth:request_uri:<token>) returned by the PAR endpoint. Valid for 10 minutes.
authorization_detailsStructured JSON object (RFC 9396) describing the authorization context — e.g. payment amount, payee, currency. Passed by the client at PAR time and propagated through the entire flow into the final JWT.
linking_idServer-generated UUID created during PAR. Acts as a stable correlation token that ties the push notification challenge back to the SCA session. Included in the push payload so the mobile app can poll for authorization status.
challengeServer-generated HMAC value cryptographically binding the authentication act to the specific transaction (amount + payee). Computed as base64(HMAC-SHA256(tokenSeed, linkingId + "|" + sha256(authorization_details))). Sent in the push notification payload, returned from the GET endpoint, and must be included in the mobile app's RSA signature.
Re-auth flowWhen linking_id is present in the workflow session AND the workflow was initiated from an OIDC /authorize call, LoginRadius skips creating a new RAAS session after MFA verification — the final token is issued by the OIDC /token endpoint instead.

End-to-End Flow


Step-by-Step Walkthrough

Step 1 — Push Authorization Request (PAR)

The client makes a back-channel POST to the PAR endpoint with all authorization parameters, including the authorization_details payload.

Endpoint

POST https://{SiteURL}/api/oidc/{OIDCAppName}/par

Request

POST /api/oidc/MyApp/par
Content-Type: application/x-www-form-urlencoded

client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&response_type=code
&scope=openid profile
&redirect_uri=https://your-app.com/callback
&authorization_details=[{"type":"lr_payment","amount":"500","currency":"EUR","payee":"Stripe"}]

Response

{
"request_uri": "urn:ietf:params:oauth:request_uri:abc123xyz",
"expires_in": 600
}

The server also generates a linking_id (UUID) internally at this step and stores it alongside the PAR state in Redis. It is not returned to the client in this response — it flows through the rest of the pipeline automatically.

PAR Request Parameters

ParameterRequiredDescription
client_id✅ RequiredYour OIDC application client ID
client_secret✅ RequiredYour OIDC application client secret
response_type✅ RequiredMust be code
scope✅ RequiredSpace-delimited scopes. Must include openid.
redirect_uri✅ RequiredCallback URL. Must be whitelisted in Admin Console.
authorization_detailsOptionalJSON array (RFC 9396) describing the authorization context. Propagated into the final JWT authorization_details claim.
nonceOptionalReplay attack prevention for id_token.
code_challengeOptionalPKCE code challenge.
code_challenge_methodOptionalPKCE method. Must be S256.

Step 2 — Authorization Request with request_uri

The client redirects the user's browser to /authorize using the request_uri reference instead of inline parameters.

Endpoint

GET https://{SiteURL}/service/oidc/{OIDCAppName}/authorize

Example Request

GET https://your-app.hub.loginradius.com/service/oidc/MyApp/authorize
?request_uri=urn:ietf:params:oauth:request_uri:abc123xyz
&client_id=YOUR_CLIENT_ID

What happens internally:

  1. LoginRadius resolves the request_uri — strips the urn:ietf:params:oauth:request_uri: prefix, looks up the PAR state from Redis using appId + clientId + token as the key.
  2. The full OidcParams (including authorization_details and linking_id) are unpacked.
  3. These are encoded into a short-lived IOFederationState JWT (the state parameter) and the user is redirected to the Identity Orchestration workflow URL.

Step 3 — Identity Orchestration Workflow

The workflow URL receives the state JWT. LoginRadius decodes it and makes linking_id and authorization_details available throughout the workflow session.

Workflow Session Data

KeyWhere AvailableValue
SharedState["linking_id"]All workflow nodes and scriptsUUID generated during PAR
SharedState["authorization_details"]All workflow nodes and scriptsRaw JSON string of the authorization_details object
SharedState["type"], SharedState["amount"], etc.All workflow nodes and scriptsIndividual fields from authorization_details, flattened for easy access in scripts

Recommended SCA Workflow Structure

Has Session
├── true → Custom Script (read user context from linking_id/SharedState)
│ → Identity Lookup (resolves user from session cookie)
│ → MFA State (push configured?)
│ ├── pushauth → Send Push Notification → Verify Push → ✅ Success
│ └── passkey → Verify Passkey → ✅ Success
└── false → Child Workflow (login flow) → loop back

The Has Session node is critical. Its true branch runs only when the user already has an active lr-session-token cookie — which is expected in SCA flows. The false branch redirects the user to authenticate first.

Re-auth Detection

A workflow MFA node is considered a re-auth (SCA) flow when both of the following are true:

  1. The workflow was initiated from an OIDC /authorize call (the outbound OIDC session is present in the workflow session).
  2. SharedState["linking_id"] is non-empty.

When re-auth is detected, LoginRadius skips creating a new RAAS session after MFA verification. The final access token is issued by the OIDC /token endpoint instead. This applies to all MFA verification nodes: Push, Passkey, OTP (Email & SMS), and Duo.

Custom Scripts and linking_id

Do not manually set linking_id in SharedState via a custom script. LoginRadius uses the combination of the outbound OIDC session and linking_id to gate the re-auth path. A script-set linking_id without a valid OIDC session will not trigger re-auth (the guard checks both conditions), but it could cause unexpected behavior in other nodes that read linking_id.


Step 4 — Push Notification with authorization_details

When the workflow reaches the Send Push Notification node, LoginRadius includes linking_id and the display title/message in the push payload sent to the mobile device.

Push Notification Payload (delivered to mobile app)

{
"title": "Confirm Payment",
"message": "Confirm Payment",
"secondfactorauthenticationtoken": "<2fa-token>",
"browser": "Chrome",
"location": "Toronto, Canada",
"timestamp": 1714000000,
"identifier": "user@example.com",
"apikey": "<app-api-key>",
"linking_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
"challenge": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols="
}

linking_id and challenge are only present for SCA flows (when authorization_details were passed at PAR time). Standard push MFA flows are unaffected.

The linking_id field allows the mobile app to call the authorization details endpoint to display the full payment/authorization context to the user before they approve or decline. The challenge field is a server-generated HMAC that cryptographically binds the user's approval to the specific transaction — the app must include it in the RSA signature when approving.


Step 5 — Get Authorization Details (Mobile App Polling)

The mobile app uses the linking_id from the push payload to fetch the authorization_details and display them to the user.

Endpoint

GET https://{SiteURL}/login/2fa/par/{linking_id}

Headers

HeaderValue
X-LoginRadius-ApikeyYour LoginRadius API key

Example Request

GET /login/2fa/par/f47ac10b-58cc-4372-a567-0e02b2c3d479
X-LoginRadius-Apikey: YOUR_API_KEY

Success Response

{
"authorization_details": {
"type": "lr_payment",
"amount": "500",
"currency": "EUR",
"payee": "Stripe"
},
"challenge": "K7gNU3sdo+OL0wNhqoVWhr3g6s1xYv72ol/pe/Unols="
}

The challenge value returned here matches the one in the push notification payload. The mobile app should display authorization_details to the user and use challenge when computing the approval signature.

Error Responses

Error CodeDescription
15linking_id not found — no 2FA session exists for this identifier
172FA token has expired

This endpoint validates the linking_id by looking it up in the active 2FA session store. It does not require a user access token — only the app API key.


Step 6 — Push Verification & Re-auth Completion

The mobile app calls the push update endpoint to approve or decline.

Endpoint

PUT https://{SiteURL}/login/2fa/push

Mobile App Signing (SCA flows)

When challenge is present in the push notification, the mobile app must include it in the RSA-signed message:

FlowSigned message
Standard push MFA (no challenge)SHA256("Approved" + secondfactorauthenticationtoken)
SCA push (challenge present)SHA256("Approved" + secondfactorauthenticationtoken + challenge)

The PUT request body remains the same in both cases — only the signature computation changes:

{
"verify": "Approved",
"secondfactorauthenticationtoken": "<2fa-token>",
"signature": "<base64-rsa-signature>"
}

The app does not need to send challenge back in the request body. The server retrieves the stored challenge from its own database and uses it for signature verification.

After the user approves, the workflow's Verify Push node detects re-auth (linking_id present + outbound OIDC session present), skips DoLogin, and completes the workflow successfully. The user is redirected back to /authorize with an authorization_code.


Step 7 — Token Exchange

The client exchanges the authorization_code for tokens. The authorization_details from the PAR request are included as a claim in the returned JWT.

Endpoint

POST https://{SiteURL}/api/oidc/{OIDCAppName}/token

Success Response

{
"access_token": "<JWT>",
"token_type": "Bearer",
"expires_in": 3600,
"id_token": "<JWT with authorization_details claim>",
"refresh_token": "<refresh-token>"
}

Decoded id_token payload (example)

{
"sub": "user-uid",
"iss": "https://your-app.hub.loginradius.com/",
"aud": "YOUR_CLIENT_ID",
"iat": 1714000000,
"exp": 1714003600,
"authorization_details": {
"type": "lr_payment",
"amount": "500",
"currency": "EUR",
"payee": "Stripe"
},
"linking_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479"
}

Data Flow Summary

Client PAR Request
→ authorization_details (JSON string)
→ linking_id (generated server-side, UUID)


Redis (PAR state, keyed by appId:clientId:token, TTL 10min)


/authorize → IOFederationState JWT (state param in workflow URL)


Workflow SharedState
├── SharedState["linking_id"] = "f47ac10b-..."
├── SharedState["authorization_details"] = "{\"type\":\"lr_payment\",...}"
├── SharedState["type"] = "lr_payment" ← flattened
├── SharedState["amount"] = "500" ← flattened
└── SharedState["currency"] = "EUR" ← flattened

├── UserInfo["linkingId"] → SecondFactorToken.LID (MongoDB)
├── UserInfo["authorizationDetails"] → SecondFactorToken.AD (MongoDB)
├── UserInfo["scaChallenge"] → SecondFactorToken.CHG (MongoDB)
│ challenge = HMAC-SHA256(tokenSeed, linkingId + "|" + sha256(authDetails))
└── PushNotification { linking_id, challenge } → Mobile App


GET /login/2fa/par/{linking_id}
→ { authorization_details, challenge }


Mobile App signs:
SHA256("Approved" + 2faToken + challenge)
with RSA private key (never leaves device)


PUT /login/2fa/push { verify, 2faToken, signature }
→ Server verifies RSA signature using stored CHG


/token → id_token JWT claim: authorization_details + linking_id

Differences from Standard Authorization Code Flow

AspectStandard Auth Code FlowPAR + SCA Flow
Authorization params locationBrowser URL (query string)Back-channel POST to /par
authorization_details supportNot availableAvailable via RFC 9396
User session requirementNot required (fresh login)User must already be authenticated (Has Session = true)
MFA outcomeNew RAAS session createdRe-auth: no new session, OIDC /token issues final JWT
linking_idNot presentServer-generated; in push payload + JWT claim
Dynamic linking (PSD2 SCA)Not applicablechallenge cryptographically binds user's RSA signature to the specific amount + payee
Mobile app contextNot applicableGET /login/2fa/par/{linking_id} returns payment/action details + challenge