# Backchannel Authentication (CIBA)

**Client-Initiated Backchannel Authentication (CIBA)** ([CIBA Core 1.0](https://openid.net/specs/openid-client-initiated-backchannel-authentication-core-1_0.html)) is an OpenID Connect extension that lets a client application trigger user authentication on a separate device, without any browser redirect.

In every traditional OpenID Connect flow, the same device that initiates the request also completes the sign-in. CIBA breaks that assumption. A **consumption device** (for example, a call center terminal or a point-of-sale terminal) starts the request. The user authenticates on an **authentication device** (for example, their phone). The two devices share no direct connection: ThunderID coordinates between them.

ThunderID supports CIBA in **poll mode** only. The `login_hint_token` hint type is defined in the specification but is **not** supported; use `login_hint` or `id_token_hint` instead.

Typical use cases include:

- A call center agent verifying a caller's identity, with the caller approving on their phone.
- A bank teller initiating a payment authorization, with the customer approving on their mobile banking app.
- A point-of-sale terminal requesting payment authentication, with the customer approving on their phone.

## How It Works

The flow proceeds in three phases:

**Phase 1: Initiation**

1. The client sends `POST /oauth2/bc-authorize` with a user hint (for example, `login_hint`), the requested scopes, and an optional `resource` that binds the request to a single resource server.
2. ThunderID validates the request and sends a notification to the user's authentication device.
3. ThunderID returns an `auth_req_id`, a polling interval, and an expiry time.

**Phase 2: User Authentication**

4. The user reviews the request on their authentication device and approves or denies it.
5. ThunderID records the decision and marks the `auth_req_id` accordingly.

**Phase 3: Token Retrieval**

6. The client polls `POST /oauth2/token` with `grant_type=urn:openid:params:grant-type:ciba` until the user has acted.
7. ThunderID returns an access token and, when `openid` is in the scope, an ID token.

How ThunderID Implements It

| Aspect | Behavior |
|---|---|
| Backchannel authentication endpoint | `POST /oauth2/bc-authorize` |
| Token endpoint | `POST /oauth2/token` |
| Grant type | `urn:openid:params:grant-type:ciba` |
| Client type | Confidential clients only |
| User hint | Exactly one of `login_hint` or `id_token_hint` must be provided. The specification also defines `login_hint_token`, but it is not yet supported; the server returns `invalid_request` when provided. |
| Poll mode | Only poll mode is supported; push mode is not implemented |
| Default `expires_in` | 120 seconds |
| Maximum `expires_in` | 600 seconds; ThunderID clamps any `requested_expiry` value above this limit |
| Default polling interval | 5 seconds |
| `slow_down` | Returned when the client polls faster than the current interval; the client must permanently increase the interval by 5 seconds |
| Scope requirement | Must include `openid` |
| Resource binding (RFC 8707) | An optional `resource` binds the request to one resource server at initiation. When a permission scope is requested without `resource`, the configured `defaultResourceServer` applies; with no default, the request is rejected with `invalid_target`. The access token's `aud` is the bound resource server identifier, and its permissions are limited to that server. OIDC-only requests without `resource` use the application's `token.accessToken.defaultAudience`, falling back to `client_id`. A `resource` supplied when polling must match the binding |
| `binding_message` | Optional; maximum 256 printable characters; ThunderID generates a default message when omitted |
| Token issuance | Access token always issued; ID token issued when `openid` is in the scope; refresh token issued when `refresh_token` grant is configured on the client |
| One-time use | ThunderID atomically marks the `auth_req_id` as consumed on the first successful token exchange; subsequent polls return `invalid_grant` |
| Discovery | `backchannel_authentication_endpoint` is advertised in the OIDC discovery document |

## Enable CIBA on a Client

**Console**

1. Open **Applications** or **Agents** in the ThunderID Console and select your client.
2. Open the **Advanced Settings** tab.
3. Under **Grant Types**, select `urn:openid:params:grant-type:ciba`.
4. Choose a confidential **Token Endpoint Authentication** method. See [Client Authentication Methods](https://thunderid.dev/docs/guides/protocols/client-authentication-methods.md).
5. Save.

**Dynamic Client Registration**

```http
POST /oauth2/dcr/register
Content-Type: application/json

{
  "client_name": "Call Center App",
  "grant_types": ["urn:openid:params:grant-type:ciba"],
  "token_endpoint_auth_method": "client_secret_basic",
  "scope": "openid profile"
}
```

## Run the Flow

### Step 1: Initiate Backchannel Authentication

```bash
curl -X POST https://thunderid.example.com/oauth2/bc-authorize \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "scope=openid profile" \
  -d "login_hint=user@example.com" \
  -d "binding_message=Approve login from call center"
```

Response:

```json
{
  "auth_req_id": "a1b2c3d4-...",
  "expires_in": 120,
  "interval": 5
}
```

### Step 2: Poll the Token Endpoint

Poll until the user approves, the request expires, or an error is returned.

```bash
curl -X POST https://thunderid.example.com/oauth2/token \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=urn:openid:params:grant-type:ciba" \
  -d "auth_req_id=a1b2c3d4-..."
```

**While the user has not yet authenticated**, the server returns:

```json
{
  "error": "authorization_pending",
  "error_description": "The user has not yet completed authentication"
}
```

Wait for the interval (or a longer interval after a `slow_down` response), then retry.

**When the user authenticates**, the server returns:

```json
{
  "access_token": "eyJhbGciOiJSUzI1NiI...",
  "id_token": "eyJhbGciOiJSUzI1NiI...",
  "token_type": "Bearer",
  "expires_in": 3600
}
```

## Polling Error Reference

| Error | Meaning | Action |
|---|---|---|
| `authorization_pending` | User has not yet acted. | Retry after the current interval. |
| `slow_down` | Client is polling too frequently. | Increase the interval permanently by 5 seconds, then retry. |
| `expired_token` | The `auth_req_id` has expired. | Stop polling; the request cannot be recovered. |
| `access_denied` | The user denied the request. | Stop polling; restart the flow if appropriate. |
| `invalid_grant` | The `auth_req_id` has already been consumed. | Stop polling; the flow is complete. |

## Related Guides

- [Client Authentication Methods](https://thunderid.dev/docs/guides/protocols/client-authentication-methods.md), required for confidential clients
- [Resource Indicators](https://thunderid.dev/docs/guides/protocols/resource-indicators.md), bind the backchannel request to a single resource server
- [OpenID Connect](https://thunderid.dev/docs/guides/protocols/openid-connect.md), add `openid` to the scope to receive an ID token
- [Server Metadata](https://thunderid.dev/docs/guides/protocols/server-metadata.md), the `backchannel_authentication_endpoint` field in the OIDC discovery document
- [Claims & Scopes](https://thunderid.dev/docs/guides/protocols/claims-and-scopes.md), configure which claims appear in the issued tokens
