Sign in with Veil
Let people sign in to your app with their Veil card. The login is a signature of their device key, and you can check it against the public log yourself.
Sign in with Veil is an OpenID Connect provider with issuer https://veil.rip. Any OpenID Connect library works with the discovery document. The identity is the card: sub is the card's number, the same for the card's whole life.
cURL
curl -fsS 'https://veil.rip/.well-known/openid-configuration'The flow#
- Register the app on /developers (a card answers for it, and the consent page says Verified when the card proves the app's domain) or with POST /oidc/register (unverified, 30 days).
- Send the browser to /oidc/authorize with the code flow and PKCE S256. PKCE is required for every app, confidential ones too.
- On the consent page the person's device key signs a consent text that names your client_id, redirect URI, scopes, nonce, the hash of your state and your code_challenge. That signature is the login. A browser without a card makes one or adds itself, and comes back.
- At your redirect URI check state and iss, then exchange the code at /oidc/token with the verifier. Access tokens last 10 minutes; refresh tokens rotate at every use.
Scopes#
- openid: the card's number (sub).
- profile: the name, the picture, the alias (preferred_username, or u<n>) and the card's address.
- veil: veil_version and veil_log (what the login was checked against), veil_good_standing with its reasons, and veil_consent, the signed consent.
- veil:accounts: the verified accounts, [{platform, id, handle, url, verified_at}].
- veil:orgs: the organisations, [{n, name, role}]. veil:keys: the active device keys and the fingerprints of the public keys from elsewhere.
Your app never gets the person's private key, and there is no password: they have none.
What to verify#
- The ID token: the signature with the JWKS key its kid names and that key's alg (EdDSA or ES256; set id_token_signed_response_alg in your library), iss https://veil.rip, aud and azp your client_id, exp, and your nonce. Many libraries skip the signature of a token that comes straight from the token endpoint, so turn the check on (in openid-client, enableNonRepudiationChecks).
- Without trusting the server for the login (scope veil): veil_consent.text begins with veil-rubrica:oidc-consent:v1; its JSON names your client_id, your nonce, SHA-256 of your state, your own code_challenge, card equal to sub and key equal to veil_consent.key; its Ed25519 signature verifies with that key.
- Against the public log: GET /v1/keys/{key} says which version added the key, and GET /v1/identities/{sub} gives the card's token and its current version. For each version from that one to the current one, GET /v1/identities/{sub}/versions/{v}/proof must hold an entry that names the card's token and the version, chains to the one before, and sits under a checkpoint the log signed and its witnesses cosigned. The first adds the key as a device, none after it revokes the key, and veil_version's entry is at veil_log.index. The guide has the code.
- Before you rely on the person now, read userinfo: veil_good_standing turns false at once for a suspension, a takeover window, a closing card or a revoked login key.
CORS covers the discovery document and the JWKS only. A browser app exchanges its code through its own server. API keys play no part here.
Reference#
- GETRead the OpenID configurationThe discovery document of Sign in with Veil: issuer https://veil.rip, the endpoints, scopes, algorithms and methods.
/.well-known/openid-configuration - GETRead the provider's signing keysThe public keys that sign ID tokens: one Ed25519 (EdDSA) and one P-256 (ES256), plus the previous pair for 30 days after a rotation.
/oidc/jwks - GETStart a sign-inThe authorization endpoint. It checks the app and its redirect URI, keeps the request 30 minutes and shows the consent page, where the person's device key signs.
/oidc/authorize - POSTExchange a code or a refresh tokenThe token endpoint: the code with its PKCE verifier, or a refresh token, for an access token, an ID token and a new refresh token.
/oidc/token - GETRead the person's claimsThe same claims as the ID token, from the card's state now: a revoked key, a takeover or a suspension changes the answer at once.
/oidc/userinfo - POSTRevoke a tokenRFC 7009: the app revokes one of its tokens. A refresh token ends every token of its login.
/oidc/revoke - POSTIntrospect a tokenRFC 7662, for confidential apps: whether one of the app's own tokens is active now.
/oidc/introspect - POSTRegister an app without a cardRFC 7591, open: an app no card answers for. The consent page calls it unverified, and it is gone 30 days after registration.
/oidc/register - GETSign out of an appRP-initiated logout. With id_token_hint the tokens of that login end; the browser goes back only to an address the app registered exactly.
/oidc/logout - GETList a card's appsThe apps a card registered for Sign in with Veil, without their secrets.
/v1/developer/accounts/{n}/oidc-clients - POSTRegister an appA person or an organisation owner signs the registration. A confidential app with client_secret_basic gets its secret once.
/v1/developer/accounts/{n}/oidc-clients - GETList a person's signed-in appsThe apps the card signed in to, with the last five logins of each and the consent its device key signed, so the owner can audit every login.
/v1/identities/{n}/oidc-apps