veil

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#

  1. 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).
  2. Send the browser to /oidc/authorize with the code flow and PKCE S256. PKCE is required for every app, confidential ones too.
  3. 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.
  4. 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#

  1. 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).
  2. 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.
  3. 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.
  4. 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#

  • GET
    Read the OpenID configurationThe discovery document of Sign in with Veil: issuer https://veil.rip, the endpoints, scopes, algorithms and methods.
    /.well-known/openid-configuration
  • GET
    Read 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
  • GET
    Start 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
  • POST
    Exchange 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
  • GET
    Read 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
  • POST
    Revoke a tokenRFC 7009: the app revokes one of its tokens. A refresh token ends every token of its login.
    /oidc/revoke
  • POST
    Introspect a tokenRFC 7662, for confidential apps: whether one of the app's own tokens is active now.
    /oidc/introspect
  • POST
    Register 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
  • GET
    Sign 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
  • GET
    List a card's appsThe apps a card registered for Sign in with Veil, without their secrets.
    /v1/developer/accounts/{n}/oidc-clients
  • POST
    Register 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
  • GET
    List 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