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. |