Software not built for the hub — Grafana, GitLab, Nextcloud, Outline, oauth2-proxy, most vendor tools with an "OpenID Connect" switch — can use the hub as its
identity provider. Apps → Connect an app →
Other software:
pick the app (Grafana, GitLab, Nextcloud, Outline, oauth2-proxy or "other"), paste its address — the callback URI and the menu entry are prefilled, and after Connect the wizard shows the exact settings block to paste into that app. Provider values and signing keys live under Settings → Sign-in for other apps. Underneath: name, redirect URIs (exact match), server app (client secret, shown once, stored as a SHA-256 fingerprint) or browser app (no secret, PKCE mandatory), token signature RS256 (default — mandatory in the standard, accepted everywhere) or ES256. The client is an ordinary connector underneath:
lanes decide which claims it receives (who signed in →
sub, email, email_verified, name, given_name, family_name, preferred_username; job profile →
title, department, division, organization, city, countryCode, managerName, employeeNumber; groups →
groups; hub role →
hub_role), the
access policy decides who may sign in, and a lock-out refuses the code, the token and userinfo within seconds.
Where it shows up: in the Apps list like every other app (type "other software"; Edit → Technical holds client id, redirect URIs, secret) and on
the menu as "signed in" — opening the entry opens the app, whose login sends the person through the hub without a second prompt (consent once).
Flow. Authorization Code only (
response_type=code), PKCE S256 supported and required for public clients. The app sends the browser to
<issuer>/oidc/authorize (the backend forwards it to the sign-in view on the frontend); the person signs in with their passkey (or existing session), sees one consent card — which app, as whom, which lanes — and is sent back with a 32-byte single-use
code bound to client, redirect URI, nonce, PKCE challenge and person, valid 60 s. The app's backend posts it to
<issuer>/oidc/token (
client_secret_basic,
client_secret_post or
none +
code_verifier) and receives an
id_token (15 min;
iss, aud, iat, exp, auth_time, nonce, amr:["passkey"] + claims) and an opaque
access token (1 h) for
/oidc/userinfo. Discovery at
<issuer>/.well-known/openid-configuration, keys at
/oidc/jwks. All HTTP endpoints run as update calls (consensus, no certification gap); the token endpoint allows 600 successful exchanges per client per 5 minutes (what bounds signing work — secrets and codes are 256-bit, guessing is not the threat). Codes and access tokens are stored under their SHA-256, so a state dump holds no live credential; a code that arrives with a verifier although it was issued without a challenge is refused (PKCE-downgrade protection); the token endpoint refuses repeated parameters and mixed client authentication; the sign-in page refuses to work inside another site's frame (
frame-ancestors 'none' + a runtime check), honours
prompt=consent/
login by always asking, and rejects
prompt=none. Failed exchanges (wrong secret, unknown code, PKCE failure, foreign code) are journaled, throttled per client.
Keys. One RSA-2048 key and one P-256 key per hub, generated in-canister from IC randomness: the RSA key by a resumable prime search that runs in the background (≈ 60 candidates per slice, one to three minutes, once — see
keys). Owners can rotate; the previous RSA key stays in the JWKS for 24 hours. The
issuer is the backend canister's URL until an owner sets a custom domain under Settings → Sign-in for other apps → Addresses — set it before the first app, changing it later means reconfiguring every app.
Identity lifecycle. Email is the current identity key in bundled apps. Do not reassign a former employee’s address to a different person: app history can follow that address even after deleting the Hub entry. Use a distinct address until immutable person IDs and migration are implemented.
Subject. sub is an opaque random id allocated once per e-mail address — because the e-mail
is the hub's identity. Consequence, stated plainly: an address re-issued to a new hire inherits the old subject at every app unless the old person was purged from the hub first (purging retires the subject); an e-mail change gives a new subject.
amr says
passkey for passkey sessions and
fed for company-SSO sessions.
Not yet. No refresh tokens (apps re-authorise silently; passkey sessions make that a redirect without a prompt), no
end_session_endpoint or back-channel logout — an app's own session lives as long as the app decides, the same limit Okta has —,
max_age is ignored and
prompt=login cannot force a fresh passkey (it asks for consent instead), no
picture claim, no PS256/HS256, the ES256 key cannot be rotated yet, no dynamic client registration ever, no SAML ever. SCIM
out (the hub provisioning people into other apps) is the planned second step.