Authorization Code flow

The Authorization Code flow is the canonical way to integrate your web application with Truora Pass: you redirect the user’s browser to Truora Pass, the user signs in and consents, and Truora Pass redirects back to you with a single-use code that your server exchanges for tokens.

Before you start you need a registered application with a client_id, client_secret, and at least one registered redirect_uri — see Registering your application. For the base URLs, see the Hosts table in Getting started.

The flow, end to end

  1. Your application redirects the user’s browser to the Truora Pass /authorize URL.
  2. The user authenticates in Truora Pass and consents to the requested scopes.
  3. Truora Pass redirects the browser back to your redirect_uri with code and state.
  4. Your server verifies state and exchanges the code at POST /v1/oauth2/token.
  5. Your server calls GET /v1/oauth2/userinfo with the access token to read the user’s verified identity.

Step 1 — Build the authorize URL

Redirect the user’s browser to the Truora Pass IdP host (this is a plain browser navigation, not an API call):

https://pass.truora.com/authorize?client_id=WLT_APP_your_client_id&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=code&scope=openid%20profile%20email%20phone%20identity%20biometric%20background&state=6d2f8a41c09e5b73d4a1f6e28c0b9d57
Parameter Value
client_id Your WLT_APP_... client ID.
redirect_uri Must exactly match one of your registered redirect_uris.
response_type Must be code.
scope Space-delimited (URL-encoded) list of scopes. Every scope must be registered for your application. See the Scopes reference.
state A fresh unguessable random value you generate per request — for example 16 random bytes, hex-encoded.

Note: state is your CSRF protection. Generate it server-side with a cryptographically secure random generator, bind it to the user’s session (for example in a cookie or server-side session), and reject any callback whose state does not match. Never reuse a state value.

Advanced optional parameters: acr_values (request a minimum authentication strength, urn:truora:acr:0 through urn:truora:acr:3) and max_age (maximum Truora Pass session age in seconds before the user must re-authenticate).

Step 2 — The user authenticates and consents

Truora Pass handles sign-in, identity verification, and the consent screen. Your application does nothing during this step.

Step 3 — Handle the callback

On success, Truora Pass redirects the browser to your redirect_uri with the code and your state:

GET https://app.example.com/callback?code=<authorization code>&state=6d2f8a41c09e5b73d4a1f6e28c0b9d57
  1. Verify that state equals the value you generated in step 1. If it does not match, abort — do not exchange the code.
  2. Read code and pass it to your server for the token exchange. Tokens never appear in URLs — only this single-use code does.

If the request fails or the user denies consent, the callback carries error and error_description instead of code:

GET https://app.example.com/callback?error=<error code>&error_description=<human-readable description>&state=6d2f8a41c09e5b73d4a1f6e28c0b9d57

Note: The authorization code is single-use and expires 10 minutes after it is issued. Exchange it promptly; a second exchange of the same code fails.

Step 4 — Exchange the code for tokens

From your server (never from the browser — this request carries your client_secret), call the token endpoint with a JSON body:

curl -X POST https://api.pass.truora.com/v1/oauth2/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "authorization_code",
    "code": "<code from the callback>",
    "client_id": "WLT_APP_your_client_id",
    "client_secret": "<your client secret>",
    "redirect_uri": "https://app.example.com/callback"
  }'

redirect_uri must match the one used in the authorize URL. A successful response looks like this:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "scope": "openid profile email phone identity biometric background",
  "id_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "9f2c1a7e4b8d0356e1c9..."
}
Field Meaning
access_token Bearer token for GET /v1/oauth2/userinfo. Expires after expires_in seconds (3600 by default).
token_type Always Bearer.
scope The scopes actually granted.
id_token Present only when the openid scope was granted.
refresh_token Always returned on this grant. Use it to obtain new access tokens without sending the user back through authorization — see Refresh tokens.

Note: Treat the tokens as opaque strings. All identity claims are released through the userinfo endpoint — do not decode or verify the tokens yourself.

Error responses from this grant use the Truora Pass error envelope ({"code": ..., "status": ..., "message": ..., "error_code": ...}):

HTTP status error_code Meaning
401 bad_credentials Invalid client_id / client_secret.
400 validation_error Invalid or expired authorization code, or invalid client/redirect_uri — including a code that was already used.

See Errors and troubleshooting for the full error reference.

Step 5 — Read the user’s identity

Call the userinfo endpoint with the access token:

curl https://api.pass.truora.com/v1/oauth2/userinfo \
  -H "Authorization: Bearer <access_token>"

The response is a JSON object whose fields depend on the granted scopes (it always includes sub). With the scopes from this example, an abridged response looks like:

{
  "sub": "WLT_USR_01ARZ3NDEKTSV4RRFFQ69G5FAV",
  "email": "maria@example.com",
  "email_verified": true,
  "phone_number": "+573100000000",
  "phone_number_verified": true,
  "name": "Maria Lopez",
  "first_name": "Maria",
  "last_name": "Lopez",
  "birthdate": "1992-04-15",
  "identity_verified": true,
  "verification_level": "basic",
  "face_enrolled": true,
  "liveness_check_passed": true,
  "trust_level": "high",
  "background_verified": true
}

The values above are illustrative. See UserInfo and claims for the complete claim map per scope, and Consuming verification results for how to interpret the verification claims.

Try it

The CapiBank demo application at https://demo.pass.truora.com implements exactly this sequence — the URLs and requests in this guide mirror it.

Next steps

  • Keep the user signed in without repeated redirects: Refresh tokens.
  • Embed Truora Pass in your own page instead of redirecting: Embedded SDK.
  • Authenticate users with no browser at all: CIBA flow.