Skip to content

OpenID Connect

Sign in with Revoked. A service signs people in with an ordinary OpenID Connect client library: the authorization code flow with PKCE, with the service identified by its origin and no registration. Each service sees its own subject for a person, so services cannot match their users across each other. Vault entries a service asks for are given as grants it reads with a token, never as copies. Every sign-in is confirmed with a passkey; there is no session at the provider.

GET /.well-known/openid-configuration

Provider metadata

Unauthenticated

Callable with no credential.

The discovery document client libraries start from. The issuer is https://<domain>. A server reached at localhost answers as http://localhost:<port>, so a development setup with the real DOMAIN works; any other Host header falls back to the domain. Only the code flow with PKCE (S256) and public clients (token_endpoint_auth_methods_supported: none) are offered; subjects are pairwise.

Responses

Status Meaning
200 The provider's metadata.

GET /oidc/jwks

ID-token signing keys

Unauthenticated

Callable with no credential.

The key ID tokens are signed with (RS256). It is not the root key: it is a separate key the root key certifies. revoked_certification carries that certification in the format of every root-key statement — payload (base64url of a JSON body naming the domain and the key's RFC 7638 thumbprint as kid) and signature (hex) — so a client that walks the DNS pin to the root key can tie the signing key to the domain as well. Ordinary clients ignore the member.

Responses

Status Meaning
200 A JSON Web Key Set.

Response fields

Field Type Notes
keys array of object

GET /oidc/authorize

Authorization endpoint

Unauthenticated

Callable with no credential.

The page a service sends the browser to. It shows what the service asks for, has the person confirm with their passkey, and lets them choose what to give: each optional scope can be declined, and each requested vault key can be answered with one of their entries or left out.

client_id is the service's origin (https://game.example), and redirect_uri must lie on that origin. Until both check out, nothing is sent back: the page shows an error instead of redirecting. Every later refusal goes back to redirect_uri with error, state and iss.

prompt=none always answers login_required, since every sign-in needs the passkey.

Parameters

Name In Required Notes
client_id query yes The service's origin.
redirect_uri query yes
response_type query yes
scope query yes openid, plus any of profile, email, revoked_identity.
code_challenge query yes
code_challenge_method query yes
state query no
nonce query no
prompt query no
claims query no JSON. {"userinfo": {"revoked_links": {"values": ["shipping_address"]}}} asks for vault entries by key, at most 20.

Responses

Status Meaning
200 The sign-in and consent page.
302 A refusal sent back to the service.
400 The service's origin or redirect address did not check out.

POST /oidc/authorize/session

Join a passkey sign-in to a request (the page)

Unauthenticated

Callable with no credential.

Called by the authorization page once the passkey sign-in finished (/api/passkeys/login/finish, with a PKCE challenge the page made). Spends the request and the code, and returns what the consent screen shows: the account, what each scope would reveal, and the person's entries for each key asked for.

Request body

application/json

Field Type Notes
request string The id the page was rendered with.
code string
verifier string

Responses

Status Meaning
200 The consent data and a ticket for the decision.
400 Refused, with a named reason.
429 Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced.

POST /oidc/authorize/consent

Record the decision (the page)

Unauthenticated

Callable with no credential.

Spends the ticket. With approve: true, grants openid plus the listed scopes that were asked for, and gives the service a grant over the chosen entries — one link per person and service, updated on every sign-in that shares something, with a fresh token each time. The answer is the address to send the browser to: with a code, or with error=access_denied.

Request body

application/json

Field Type Notes
ticket string
approve boolean
scopes array of string
links object Requested key → the person's entry id.

Responses

Status Meaning
200 Where to send the browser.
400 oidc_request_invalid, or oidc_link_record_invalid for an entry that is not the person's.

Response fields

Field Type Notes
redirect string

POST /oidc/token

Token endpoint

Unauthenticated

Callable with no credential.

Redeems an authorization code. The code is spent the moment it is presented; client_id, redirect_uri and the PKCE verifier must match the authorization request. There is no client secret and no refresh token: the ID token proves a sign-in that just happened, and the service keeps its own session after that.

The ID token (RS256, 10 minutes) carries sub, aud, iss, iat, exp, auth_time, nonce, and the claims of the granted scopes: name, email and email_verified, and revoked_identity (the fingerprint of the person's primary identity).

Request body

application/x-www-form-urlencoded

Field Type Notes
grant_type string (authorization_code)
code string
client_id string
redirect_uri string
code_verifier string

Responses

Status Meaning
200 The tokens.
400 An OAuth error: invalid_grant, unsupported_grant_type or invalid_request.
429 Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced.

Response fields

Field Type Notes
access_token string
token_type string
expires_in integer
id_token string
scope string

GET /oidc/userinfo

Userinfo endpoint

Auth: OIDCAccessToken

The claims of the granted scopes, and revoked_links when the person shared entries:

{ "revoked_links": { "endpoint": "https://<domain>/api/grants/resolve",
    "grants": [ { "id": "…", "token": "…", "keys": ["shipping_address"] } ] } }

The service reads the values with POST /api/grants/resolve, as often as it needs them, for as long as the person does not revoke the grant. The grant token is delivered here only, never in the ID token.

Responses

Status Meaning
200 The claims.
401 invalid_token.
429 Too many attempts. Password gates, probes and challenges are limited per client, so a slug cannot be brute-forced.

POST /oidc/userinfo

Userinfo endpoint (POST)

Auth: OIDCAccessToken

The same as the GET form.

Responses

Status Meaning
200 The claims.
401 invalid_token.