# 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](/truora-pass/registering_your_application/). For the base URLs, see the Hosts table in [Getting started](/truora-pass/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](/truora-pass/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:

```bash
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:

```json
{
  "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](/truora-pass/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](/truora-pass/errors_and_troubleshooting/) for the full error reference.

### Step 5 — Read the user's identity

Call the userinfo endpoint with the access token:

```bash
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:

```json
{
  "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](/truora-pass/userinfo_and_claims/) for the complete claim map per scope, and [Consuming verification results](/truora-pass/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](/truora-pass/refresh_tokens/).
- Embed Truora Pass in your own page instead of redirecting: [Embedded SDK](/truora-pass/embedded_sdk/).
- Authenticate users with no browser at all: [CIBA flow](/truora-pass/ciba_flow/).
