Skip to main content

CIBA (Client-Initiated Backchannel Authentication)

CIBA is BKey’s core mechanism. An agent or server requests approval for a specific action; the user’s phone gets a push notification and approves with facial biometrics. BKey returns an EdDSA-signed JWT that proves the consent.

When to use it

  • An agent needs explicit human approval before a sensitive action
  • Per-transaction authorization (deploys, refunds, vault reads, DB writes)
  • Anywhere you’d replace a soft “click Allow” dialog with cryptographic consent

One-line integration

Both SDKs expose a single approve() call that hides the full two-step protocol.

TypeScript

Python

That single call initiates the CIBA request, sends the push notification, polls for the result, and returns the signed token on approval. On denial it raises (Python) or returns approved: false (TypeScript).

Flow at the protocol level

If you need to talk to the API directly, CIBA is two calls:
  1. InitiatePOST /oauth/bc-authorize with the user’s DID and the action scope.
  2. PollPOST /oauth/token with grant_type=urn:openid:params:grant-type:ciba until approved, denied, or expired.

Raw HTTP example

Response:
Then poll:

What the approval token contains

The token you receive is an EdDSA (Ed25519) JWT signed by BKey: Verify with verifyToken() from @bkey/node (TypeScript) or the JWKS at GET /oauth/jwks.

Design rules

  • One scope per sensitive action. approve:deploy, approve:refund, approve:db:drop — not a single “admin” scope. Scopes show up on the user’s phone and in audit logs.
  • Always verify server-side. approved: true means the flow completed. verifyToken() means the token is real. Don’t skip it.
  • Treat jti as a nonce. Record approved JTIs server-side; reject replays.
  • Keep expiries tight. 300s (5 min) is the default and usually the right choice. Don’t use hours-long expiries — approvals are per-action, not per-session.
  • Bind to details. Use action_details to render amounts, recipients, and resources on the approval screen.

See also