Skip to main content

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

  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

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

Phase 3: Token Retrieval

  1. The client polls POST /oauth2/token with grant_type=urn:openid:params:grant-type:ciba until the user has acted.
  2. ThunderID returns an access token and, when openid is in the scope, an ID token.
How ThunderID Implements It
AspectBehavior
Backchannel authentication endpointPOST /oauth2/bc-authorize
Token endpointPOST /oauth2/token
Grant typeurn:openid:params:grant-type:ciba
Client typeConfidential clients only
User hintExactly 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 modeOnly poll mode is supported; push mode is not implemented
Default expires_in120 seconds
Maximum expires_in600 seconds; ThunderID clamps any requested_expiry value above this limit
Default polling interval5 seconds
slow_downReturned when the client polls faster than the current interval; the client must permanently increase the interval by 5 seconds
Scope requirementMust 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_messageOptional; maximum 256 printable characters; ThunderID generates a default message when omitted
Token issuanceAccess token always issued; ID token issued when openid is in the scope; refresh token issued when refresh_token grant is configured on the client
Actor claimThe access token carries an act claim naming the client when that client is an agent, or an application with includeActClaim enabled. See Actor on the Issued Token
One-time useThunderID atomically marks the auth_req_id as consumed on the first successful token exchange; subsequent polls return invalid_grant
Discoverybackchannel_authentication_endpoint is advertised in the OIDC discovery document

Enable CIBA on a Client

  1. Open Applications or Agents in the ThunderID Console and select your client.
  2. Open the Advanced tab.
  3. Under Grant Types, select urn:openid:params:grant-type:ciba.
  4. Choose a confidential Token Endpoint Authentication method. See Client Authentication Methods.
  5. Save.

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
}

Actor on the Issued Token

The issued access token represents the user who approved the request, so sub holds that user's subject identifier. The token additionally names the client that obtained it in an act (actor) claim, so a resource server sees both the user who approved the request and the client acting for them.

Clientact claim
AgentAlways present.
Application with includeActClaim enabledPresent.
Application without includeActClaimAbsent.

The value of act.sub is the agent or application resource identifier, not the client ID. The claim appears on the access token only, never on the ID token.

{
"sub": "<user-id>",
"act": { "sub": "<agent-or-application-id>" },
"client_id": "<client-id>",
"grant_type": "urn:openid:params:grant-type:ciba"
}

When the client also holds the refresh_token grant, the refresh token records the same actor, and every access token minted from it carries the actor decided at issuance rather than the client's current includeActClaim setting.

Polling Error Reference

ErrorMeaningAction
authorization_pendingUser has not yet acted.Retry after the current interval.
slow_downClient is polling too frequently.Increase the interval permanently by 5 seconds, then retry.
expired_tokenThe auth_req_id has expired.Stop polling; the request cannot be recovered.
access_deniedThe user denied the request.Stop polling; restart the flow if appropriate.
invalid_grantThe auth_req_id has already been consumed.Stop polling; the flow is complete.

Explore with AI

ThunderID LogoThunderID Logo

Product

DocsAPIsSDKs
© Copyright Linux Foundation Europe.For web site terms of use, trademark policy and other project policies please see https://linuxfoundation.eu/en/policies.