Backchannel Authentication (CIBA)
Client-Initiated Backchannel Authentication (CIBA) (CIBA Core 1.0) 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
- The client sends
POST /oauth2/bc-authorizewith a user hint (for example,login_hint), the requested scopes, and an optionalresourcethat binds the request to a single resource server. - ThunderID validates the request and sends a notification to the user's authentication device.
- ThunderID returns an
auth_req_id, a polling interval, and an expiry time.
Phase 2: User Authentication
- The user reviews the request on their authentication device and approves or denies it.
- ThunderID records the decision and marks the
auth_req_idaccordingly.
Phase 3: Token Retrieval
- The client polls
POST /oauth2/tokenwithgrant_type=urn:openid:params:grant-type:cibauntil the user has acted. - ThunderID returns an access token and, when
openidis 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
- Dynamic Client Registration
- Open Applications or Agents in the ThunderID Console and select your client.
- Open the Advanced Settings tab.
- Under Grant Types, select
urn:openid:params:grant-type:ciba. - Choose a confidential Token Endpoint Authentication method. See Client Authentication Methods.
- Save.
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
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:
{
"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.
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:
{
"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:
{
"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, required for confidential clients
- Resource Indicators, bind the backchannel request to a single resource server
- OpenID Connect, add
openidto the scope to receive an ID token - Server Metadata, the
backchannel_authentication_endpointfield in the OIDC discovery document - Claims & Scopes, configure which claims appear in the issued tokens