Login with bkey
“Sign in with bkey” is standard OIDC authorization code + PKCE. Your site redirects to BKey, the user approves on their phone with their face, and you get back a stable identifier for that user. No user password exists anywhere in the flow — nothing to leak, phish, or reset. Your application still holds long-lived credentials of its own, issued below. Those need the usual care. This is a different flow from CIBA. Use Login with bkey to sign a human into your website. Use CIBA to get a human to approve a specific action your backend is about to take.Get credentials — self-serve, no account needed
Client registration is RFC 7591 dynamic registration. No dashboard, no account, no sales call: one request gives you aclient_id and client_secret.
Save both credentials
A successful registration returns two secrets, each shown exactly once:client_secretauthenticates your client at the OAuth endpoints.registration_access_tokenmanages the client — it is the only credential that can read, update, rotate, or delete an anonymously registered client. The client secret cannot do any of those.
registration_client_uri with it. Discard it and you lose the
ability to fix a mistyped redirect URI or rotate a leaked secret.
post_logout_redirect_uris is persisted even though the registration response
does not echo it back. Read the client with getRegisteredClient() to confirm
what was stored.Changing things later
Provided you kept the registration access token, the client is fully editable — see the lifecycle helpers in Integrate BKey:updateRegisteredClient()changesredirect_uris,post_logout_redirect_uris, and the client name.rotateClientSecret()issues a new secret, with a grace window for the old one. UsegraceHours: 0after a leak.claimRegisteredClient()transfers an anonymous client to an account, so it is no longer managed by that one token.
redirect_uris must match your
callback path exactly. For the Auth.js preset below that is
/api/auth/callback/bkey.
BKey accepts http:// redirect URIs only for loopback addresses
(http://localhost:3000/…), per
RFC 8252. Every other
origin must be HTTPS — including a LAN address, so testing against a phone on
your local network needs an HTTPS tunnel.
Use the issuer https://id.bkey.id
Next.js — the five-line version
@bkey/login
ships an Auth.js provider preset. Auth.js handles
discovery, PKCE, state, nonce, and EdDSA id_token verification.
auth.ts
AUTH_SECRET set — any high-entropy string; it encrypts the
session cookie.
session.user.id is the user’s bkey ID. A complete working app is in
examples/typescript/login-with-bkey-nextjs.
Any framework — the core helpers
handleCallback validates everything before returning: state (CSRF), the PKCE
verifier, the id_token signature against BKey’s published JWKS, plus issuer,
audience, expiry, and nonce replay.
You do not need our SDK — BKey is a standards-compliant OIDC provider and any
OIDC client library can drive this flow. Discovery already advertises
token_endpoint_auth_methods_supported (none, client_secret_post) and
id_token_signing_alg_values_supported (EdDSA). One required setting is not
in the document:
- PKCE with
S256is required at/authorize.code_challenge_methods_supportedis absent, so a library configured purely from discovery will omitcode_challengeand fail (#50).
client_secret_basic,
which the registration endpoint rewrites to client_secret_post. A client that
then sends Basic cannot redeem codes. Send
token_endpoint_auth_method: "client_secret_post" at registration (as in the
request above).
The SDK sets both of those for you.
Signing out
Clearing your own session ends the sign-in. BKey is a stateless OP: it keeps no browser session or SSO cookie of its own, so there is no “still signed in at BKey” state to worry about. Every/authorize request triggers a fresh
biometric approval on the user’s phone — a user who signs in again always
approves again.
That means the ordinary case is simple: destroy your own session and you are
done.
Two endpoints exist for the cases that aren’t ordinary, and it’s worth being
precise about which does what:
/oauth/revoke invalidates a token. If you are holding an access or refresh
token and want it to stop working — the user asked you to disconnect, you are
cleaning up after a leak — this is the call that actually ends something.
/oauth/end_session implements the
RP-Initiated Logout
redirect contract, and only that. It verifies your id_token_hint, then
redirects to a registered post_logout_redirect_uri. It does not revoke
tokens. Use it when you want the spec-standard logout hand-off; use
/oauth/revoke when you want tokens dead.
postLogoutRedirectUri must be one of the post_logout_redirect_uris you
registered, and id_token_hint is required — without it BKey renders a
confirmation page rather than redirecting.
The Auth.js snippet above keeps that id_token on the jwt token as idToken,
not on session (which the browser can read at /api/auth/session).
What you get back
Theid_token carries exactly one identity claim: sub, a stable pseudonymous
identifier for that user.
sub as an opaque string and store it as your user key. Do not parse
it, and do not constrain its length or character set — the format is not part of
the contract. No name, email, or phone number is shared; collect anything else
you need in your own onboarding, on first login.
One privacy consequence worth stating plainly: sub is stable for a user and
the same value is issued to every relying party (subject_types_supported is
["public"]). Two sites that both use Login with bkey can therefore determine
they are talking to the same person. Pairwise subject identifiers, which would
prevent that, are not currently offered.
What the user sees
On mobile, the consent page shows a button that opens the bkey app directly. This is the reliable path today. On desktop, it shows a pairing code and a QR code to scan from the phone. Note that the QR currently encodes a custom URL scheme rather than an HTTPS Universal Link, so camera scanning may not hand the request to the app — see #52. Approving from the phone works regardless. Either way the user confirms the on-screen pairing code matches the one on their phone, then approves with their face. Matching the pairing code proves the phone and this browser are in the same ceremony, so an attacker who starts a sign-in on their own machine cannot complete it with someone else’s approval. It does not prove which site is requesting the sign-in — a proxying page can display the genuine code and the comparison still succeeds.Installing the SDK
See also
- Authentication overview — every grant type at a glance
- CIBA — approving a specific action, rather than signing in
- OIDC discovery — the discovery document
- Token endpoint — exchanging the code