Skip to Content
ConceptsCIBA (Backchannel Auth)

CIBA (Client Initiated Backchannel Authentication)

The Problem: Authentication Initiated by Someone Else

Traditional OAuth 2.0 flows assume a single user interacts with a single device: the user opens a browser, enters credentials, and receives tokens — all on the same device, in the same session. This model works for web apps and mobile apps, but it falls apart in scenarios where the person requesting authentication is not the same person authenticating.

ScenarioWhy Standard OAuth Fails
Call center agent verifying a customerThe agent cannot type the customer’s password
Payment terminal at a storeThe cashier initiates, the customer approves on their phone
Smart speaker purchase”Alexa, buy more coffee” — the speaker has no screen for login
Healthcare check-in kioskThe receptionist initiates, the patient approves on their phone
Wire transfer authorizationThe bank system initiates, the account holder approves remotely

In all these cases, one party initiates the authentication request and a different party — the actual user — must approve it on a separate device. The authorization server needs to push an authentication request to the user rather than waiting for the user to visit a URL.

What CIBA Is

CIBA (Client Initiated Backchannel Authentication) is an OpenID Connect extension defined in the CIBA Core specification . It allows a client application to initiate an authentication flow for a known user, where the user authenticates on a separate authentication device (typically their phone) via a push notification, SMS, or email.

The key distinction from other OAuth flows:

  • Authorization Code + PKCE: User drives the flow from start to finish on the same device.
  • Device Flow: Client displays a code, user visits a URL and enters it. User initiates the secondary interaction.
  • CIBA: Client initiates, server pushes a notification to the user. The user only reacts.

CIBA decouples the consumption device (where the service runs) from the authentication device (where the user proves their identity).

How CIBA Works: Step by Step

Step 1: Client Sends a Backchannel Authentication Request

The client sends a POST request to the CIBA endpoint with a login_hint identifying the user and a binding_message describing the action:

POST /api/oauth/ciba HTTP/1.1 Host: auth.example.com Content-Type: application/x-www-form-urlencoded Authorization: Bearer <client_access_token> [email protected] &scope=openid profile &binding_message=Authorize account verification for agent Sarah (ref: TX-9821) &requested_expiry=120 &client_notification_token=notification-callback-token-xyz
ParameterRequiredDescription
login_hintYesIdentifies the user — email, phone number, or user ID
scopeYesRequested OAuth scopes (must include openid)
binding_messageRecommendedHuman-readable description shown to the user on their authentication device
requested_expiryOptionalHow long the auth request should remain valid (seconds). Default: 300
client_notification_tokenConditionalRequired for ping and push modes; the token Auris sends back in the callback

Step 2: Server Returns an Authentication Request ID

If the login_hint resolves to exactly one user and the client is authorized to use CIBA, the server responds:

{ "auth_req_id": "ciba_req_1a2b3c4d5e6f7g8h", "expires_in": 120, "interval": 5 }

The auth_req_id is the handle the client uses to check on the status of the authentication request.

Step 3: Server Notifies the User

Auris sends a notification to the user’s authentication device through one of the configured channels:

  • Push notification: Native mobile push (requires the user to have the authenticator app installed)
  • SMS: Text message with a link to the approval page
  • Email: Email with a link to the approval page

The notification includes the binding_message so the user knows exactly what they are approving.

Step 4: User Approves or Denies

The user sees the binding message and the requesting application’s name on their authentication device. They can:

  • Approve: The authorization server marks the CIBA request as approved and (if required) the user completes MFA
  • Deny: The authorization server marks the request as denied; the client receives access_denied on the next poll

Step 5: Client Obtains Tokens

How the client receives the tokens depends on the configured notification mode (see next section).

The Three Notification Modes

CIBA defines three ways for the client to obtain tokens after the user approves. Each makes a different trade-off between simplicity, latency, and infrastructure requirements.

Poll Mode

The simplest mode. The client polls the token endpoint at the configured interval, exactly like the Device Flow:

POST /api/auth/token HTTP/1.1 Host: auth.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn:openid:params:grant-type:ciba &auth_req_id=ciba_req_1a2b3c4d5e6f7g8h &client_id=call-center-app &client_secret=client-secret

The server responds with the same error codes as the Device Flow during polling:

ResponseMeaning
authorization_pendingUser has not yet responded
slow_downClient is polling too fast; increase interval by 5 seconds
expired_tokenThe auth_req_id has expired
access_deniedUser denied the request
Success (200)Tokens returned

Best for: Simple integrations where 5-second latency is acceptable.

Ping Mode

In ping mode, the authorization server sends an HTTP POST to the client’s registered callback URL when the user responds. The callback body contains only the auth_req_id — the client must then fetch the tokens from the token endpoint.

// Server pings the client's callback POST /ciba-callback HTTP/1.1 Host: client.example.com Content-Type: application/json Authorization: Bearer <client_notification_token> { "auth_req_id": "ciba_req_1a2b3c4d5e6f7g8h" }

The client then fetches tokens normally:

POST /api/auth/token HTTP/1.1 Host: auth.example.com Content-Type: application/x-www-form-urlencoded grant_type=urn:openid:params:grant-type:ciba &auth_req_id=ciba_req_1a2b3c4d5e6f7g8h &client_id=call-center-app &client_secret=client-secret

Best for: Production systems where the client has a publicly reachable callback URL and needs lower latency than poll mode.

Push Mode

In push mode, the authorization server pushes the actual tokens directly to the client’s callback URL. The client never calls the token endpoint.

// Server pushes tokens to the client's callback POST /ciba-callback HTTP/1.1 Host: client.example.com Content-Type: application/json Authorization: Bearer <client_notification_token> { "auth_req_id": "ciba_req_1a2b3c4d5e6f7g8h", "access_token": "eyJhbGciOiJSUzI1NiJ9...", "token_type": "Bearer", "expires_in": 3600, "id_token": "eyJhbGciOiJSUzI1NiJ9..." }

Best for: Lowest latency. However, the tokens travel over the network to the client’s callback, increasing the attack surface.

Mode Comparison

DimensionPollPingPush
LatencyHigher (polling interval)Low (server-initiated)Lowest (tokens in callback)
Client complexitySimple (polling loop)Medium (callback endpoint + token fetch)Medium (callback endpoint)
Client infrastructureNone (outbound only)Needs public callback URLNeeds public callback URL
Token securityTokens never traverse untrusted networksTokens fetched securely by clientTokens sent to callback (higher exposure)
Best forCLI tools, simple integrationsProduction web servicesReal-time systems

Auris supports all three modes. Poll mode is the default for new applications. To use ping or push mode, register a backchannel_client_notification_endpoint in the application settings and include a client_notification_token in each CIBA request.

The Binding Message

The binding_message is arguably the most important security feature of CIBA. It is a short, human-readable string that is displayed to the user on their authentication device, describing exactly what they are approving.

Why It Matters

Without a binding message, CIBA is vulnerable to confused deputy attacks: an attacker initiates a CIBA request for a victim user, and the victim sees a generic “Approve login?” prompt with no context. The victim approves, thinking it is their own login attempt, and the attacker receives tokens.

With a binding message, the victim sees:

“Authorize wire transfer of $5,000 to account ending in 7892 (ref: WT-2024-0918)”

If the victim did not initiate a wire transfer, they know to deny the request.

Best Practices

PracticeExample
Include the action being authorized”Authorize payment of EUR 49.99”
Include a reference number”(ref: TX-9821)“
Include the requesting party”Agent Sarah at ACME Bank”
Keep it under 200 charactersMobile push notifications truncate long messages
Do not include sensitive dataDo not put full account numbers or SSNs in the message

The binding_message is displayed on the user’s device, which may be a push notification visible on the lock screen. Never include passwords, full credit card numbers, or other sensitive information in the binding message.

Security Analysis

Authentication Request Lifetime

CIBA requests have a short lifetime (default 300 seconds, configurable via requested_expiry up to a server-imposed maximum). After expiry, the auth_req_id is invalid and the client must start a new flow.

Login Hint Resolution

The login_hint must resolve to exactly one user. If the hint is ambiguous (e.g., a common name matching multiple users), the server rejects the request with unknown_user_id. This prevents attacks where an attacker targets a specific user by providing a vague hint.

Notification Channel Security

The security of CIBA depends on the security of the notification channel:

ChannelRiskMitigation
Push notificationCompromised phone, notification interceptionRequire biometric unlock to approve
SMSSIM swapping, SS7 interceptionUse push notifications when possible; SMS as fallback only
EmailEmail account compromise, delivery delayUse push notifications when possible; email as fallback only

CIBA + DPoP

CIBA can be combined with DPoP to produce sender-constrained tokens. The client includes a DPoP proof when polling/fetching tokens, and the resulting access token is bound to the client’s key pair. This provides an additional layer of protection if the tokens are intercepted during delivery (especially relevant in push mode).

Comparison: CIBA vs Device Flow

Both CIBA and Device Flow authenticate a user who is not directly interacting with the client device. The fundamental difference is who initiates the secondary-device interaction:

DimensionDevice FlowCIBA
Who initiatesUser (visits a URL)Server (sends notification)
User code entryUser types code manuallyNo code entry; user just approves
Prior user registrationNot requiredRequired (server must know how to reach the user)
Anonymous usersSupportedNot supported
UX latencyHigher (user must visit URL, type code)Lower (user taps approve on notification)
Notification infrastructureNoneRequires push/SMS/email capability
Offline userCan authenticate later (within code lifetime)Cannot receive notification if offline

Auris Implementation Details

Prisma Model

model CibaAuthRequest { id String @id @default(cuid()) tenantId String applicationId String authReqId String @unique loginHint String userId String? scope String? bindingMessage String? status CibaAuthStatus @default(PENDING) notificationMode CibaNotificationMode @default(POLL) clientNotificationToken String? expiresAt DateTime interval Int @default(5) createdAt DateTime @default(now()) } enum CibaAuthStatus { PENDING APPROVED DENIED EXPIRED } enum CibaNotificationMode { POLL PING PUSH }

Configuration

CIBA is enabled per-application in the Auris Console under Applications > (select application) > Settings:

SettingDefaultDescription
Enable CIBAfalseMaster toggle
Notification modePOLLHow the client receives tokens (poll, ping, or push)
Notification channelsemailWhich channels to use for notifying users (push, sms, email)
Max request lifetime300Maximum requested_expiry in seconds
Polling interval5Minimum seconds between token endpoint polls (poll mode only)
Callback URLClient’s backchannel notification endpoint (required for ping/push)

API Endpoints

EndpointMethodDescription
/api/oauth/cibaPOSTInitiate a backchannel authentication request
/api/auth/tokenPOSTToken endpoint (supports urn:openid:params:grant-type:ciba grant type)
/hosted/ciba/approveGETUser-facing approval page (linked from notification)

Code Example: Call Center Agent Flow

async function verifyCustomerIdentity( customerEmail: string, agentName: string, referenceNumber: string ) { // Step 1: Initiate CIBA request const cibaResponse = await fetch('https://auth.example.com/api/oauth/ciba', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Bearer ${agentAccessToken}`, }, body: new URLSearchParams({ login_hint: customerEmail, scope: 'openid profile', binding_message: `Identity verification by ${agentName} (ref: ${referenceNumber})`, requested_expiry: '120', }), }) const { auth_req_id, interval, expires_in } = await cibaResponse.json() console.log(`Verification sent to ${customerEmail}. Waiting for approval...`) // Step 2: Poll for completion (poll mode) let pollInterval = interval * 1000 const deadline = Date.now() + expires_in * 1000 while (Date.now() < deadline) { await new Promise(resolve => setTimeout(resolve, pollInterval)) const tokenResponse = await fetch('https://auth.example.com/api/auth/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, body: new URLSearchParams({ grant_type: 'urn:openid:params:grant-type:ciba', auth_req_id, client_id: 'call-center-app', client_secret: 'client-secret', }), }) if (tokenResponse.ok) { const { id_token } = await tokenResponse.json() console.log('Customer identity verified.') return id_token // Contains verified user claims } const error = await tokenResponse.json() if (error.error === 'slow_down') { pollInterval += 5000 continue } if (error.error === 'authorization_pending') continue if (error.error === 'access_denied') { throw new Error('Customer denied the verification request.') } throw new Error(`Verification failed: ${error.error_description}`) } throw new Error('Verification request expired. Customer did not respond in time.') }