SenseCrypt Docs
Concepts

CIBA

Client-Initiated Backchannel Authentication — decoupled, no-browser sign-in where SenseCrypt emails the user a sign-in QR, they approve on their phone, and your backend collects the tokens.

SenseCrypt supports CIBA (OpenID Connect Client-Initiated Backchannel Authentication). CIBA is a decoupled flow: your backend starts authentication with only a hint about the user — there is no browser and no redirect. The user approves on their phone, and your backend collects the tokens afterward.

This fits scenarios like call-center verification, kiosk or point-of-sale confirmation, or any server-initiated step-up where the person is not at a browser you control.

How it fits SenseCrypt

When your backend calls the backchannel endpoint, SenseCrypt emails the sign-in QR to the resolved user. The user scans it with the Authenticator app and completes the same on-device face ceremony as an interactive sign-in. Receiving the emailed QR is itself the email-possession proof, and the face match is the biometric factor — so a CIBA sign-in carries the same assurance as a browser one.

The overall shape is: start the request (get an auth_req_id), then collect the tokens (poll, ping, or push).

The full decoupled flow — start on the consumption device, approve on the phone, collect the tokens — looks like this:

opt [ping delivery] loop [until approved, no faster than interval] alt [poll / ping delivery] [push delivery] POST /v1/idp/oidc/bc-authorizescope=openid, one hint (login_hint),binding_message?, + client auth Authenticate client, resolve delivery mode,resolve hint → email, decide sign-in vs self-signup,create pending OAuthSession (auth_req_id) Email the sign-in QR (universal link)[+ best-effort push to the user's devices] 200 { auth_req_id, expires_in, interval } User opens the emailed QR in the Authenticator(receiving the QR is the email-possession proof) Complete the on-device face ceremony(/payload → /complete) Session → authorized Notify client_notification_token (request is ready) POST {token_endpoint}grant_type=…:ciba, auth_req_id 400 authorization_pending POST {token_endpoint} (auth_req_id) 200 tokens (auth_req_id now spent, single-use) Deliver tokens out-of-band to thenotification endpoint (client never polls) Approve Face proof on device —no biometric data leaves the phone Collect tokens Consumption Device (your backend) SenseCrypt IdP User's email Phone (Authenticator)

1. Start the request

Your backend authenticates as a CIBA-enabled client and POSTs to the backchannel authentication endpoint (resolve it from discovery as backchannel_authentication_endpoint; conventionally /v1/idp/oidc/bc-authorize):

POST {backchannel_authentication_endpoint}
  scope=openid ...
  login_hint={user_email}
  binding_message={short_message}          # optional, ≤ 512 chars, shown to the user
  client_notification_token={token}        # required for ping/push
  # + your client authentication

Rules for the request:

  • The scope must include openid, and every scope must be one your client is allowed to use.
  • Provide exactly one user hint: login_hint (an email), login_hint_token, or id_token_hint. Supplying zero or more than one is an error.
  • binding_message (optional) is a short string shown to the user so they can confirm the request matches what they initiated. It is capped at 512 characters.
  • client_notification_token is required for ping and push delivery (it is how SenseCrypt calls you back); it is ignored for poll.
  • requested_expiry (optional) can shorten/lengthen the request TTL, clamped to the server's bounds.
  • user_code is not used. SenseCrypt is passwordless, so backchannel_user_code_parameter_supported is false and any stray user_code is ignored.
  • You may request an API audience with resource / audience, exactly as in the code flow (see Authorization).

On success you receive:

{
  "auth_req_id": "…",
  "expires_in": 300,
  "interval": 5
}
  • auth_req_id — the opaque handle you present when polling. It is single-use.
  • expires_in — seconds until the request expires (default 300 s for an enrolled-user sign-in; longer when the flow also involves self-signup).
  • interval — minimum seconds between polls (default 5 s; omitted for push).

See the bc-authorize API reference.

2. Collect the tokens

Poll (and ping) delivery

Poll the token endpoint with the CIBA grant, no faster than interval:

POST {token_endpoint}
  grant_type=urn:openid:params:grant-type:ciba
  auth_req_id={auth_req_id}
  # + your client authentication

Handle these responses:

ResponseMeaningWhat to do
200 with tokensThe user approved.Done — the auth_req_id is now spent.
400 authorization_pendingNot approved yet.Keep polling at interval.
400 slow_downYou polled too fast.Back off, then continue polling.
400 expired_tokenThe request expired.Start a new request.
400 access_deniedThe user declined (or the ceremony failed).Stop.

The success body is a standard token response (access_token, id_token, token_type: "Bearer", expires_in, scope, and refresh_token if offline_access was granted). All the token semantics apply.

With ping delivery, SenseCrypt notifies your client_notification_token endpoint when the request is ready, and you then collect the tokens at the token endpoint exactly as with poll.

Push delivery

With push delivery, SenseCrypt delivers the tokens directly to your notification endpoint out-of-band. Push clients do not poll the token endpoint — a push client that tries to poll is rejected.

Delivery modes

SenseCrypt advertises poll, ping, and push in its discovery metadata (backchannel_token_delivery_modes_supported: ["poll", "ping", "push"]). Your client is registered for exactly one mode, and that registration — not a request parameter — decides the delivery behavior.

ModeYou poll?Callbackclient_notification_token
pollYes, at intervalnonenot used
pingYes, after the callbacknotify-onlyrequired
pushNotokens delivered out-of-bandrequired

Edge cases and gotchas

  • Respect interval. Polling faster earns a slow_down; keep to the advertised cadence (default 5 s).
  • auth_req_id is single-use and time-boxed. After a successful 200, or after expires_in elapses, it's spent — start a fresh request rather than retrying.
  • Exactly one hint. Zero or two of login_hint / login_hint_token / id_token_hint is a request error.
  • Ping/push need a notification token. Without client_notification_token, a ping/push request is rejected.
  • binding_message is bounded. Over 512 characters is refused (it would otherwise bloat the dispatched email); keep it short and human-readable.
  • CIBA requests are rate-limited per client and per client+target-email QR dispatch, so a runaway loop can't spray sign-in emails at a user.
  • The user must be enrolled. The hint has to resolve to a user who can complete the face ceremony; a non-enrollable target fails closed like any other gate.

Set it up

See the CIBA backchannel integration guide for a concrete request-and-poll example, and OIDC & OAuth 2.0 for client authentication.

On this page