Authentication
How tokens work, what's public, and what to expect when things go wrong.
Endpoint schemas and parameter details live in the API Reference. This page covers authentication behavior — how tokens work, what's public, and what to expect when things go wrong.
Token Transport
All protected endpoints require an access token. Two transports are supported:
Authorization header (programmatic clients):
Authorization: Bearer <access_token>Session cookie (browser clients):
Cookie: session=<access_token>The Bearer prefix is case-sensitive — bearer is not accepted. The token is a base64-encoded 32-byte value issued by the login flow. It is opaque — do not parse or decode it. When both are present, the Authorization header takes precedence.
Where To Get a Token
Tokens are issued at the end of the OPAQUE login handshake:
POST /auth/opaque/authenticate-start— send blinded login requestPOST /auth/opaque/authenticate-finish— complete handshake, receiveaccess_tokenandrefresh_token
The access token expires after 15 minutes. When it expires, refresh it — do not re-authenticate.
Token Refresh
POST /auth/tokens/refresh exchanges a valid refresh token for a new access token and a new refresh token. This endpoint requires no Authorization header — the caller's access token may already be expired.
Browser clients send the refresh token automatically via the kyndex_rt HttpOnly cookie. Programmatic clients pass it in the request body.
The old refresh token is permanently revoked on each call. If an attacker replays a stolen refresh token after the legitimate client has already rotated, the server rejects the stale token with 401.
For the full OPAQUE registration and login protocol, see the Authentication reference under Getting Started.
Token Expiry: 401 vs. 403
| Status | Meaning | What to do |
|---|---|---|
401 Unauthorized | Token is missing, malformed, expired, or revoked | Refresh the token via /auth/tokens/refresh. If that also fails, re-authenticate. |
403 Forbidden | Token is valid, but you lack permission for this resource | Check your role (e.g. entity admin vs. member). This is not a token problem. |
The API will never return 401 for a permission issue or 403 for an expired token. The distinction is strict.
Public (Unauthenticated) Endpoints
The API enforces deny-by-default authentication. Every endpoint is protected unless explicitly opted out. The following endpoints require no Authorization header:
Auth
| Endpoint | Why it's public |
|---|---|
POST /auth/challenges | OPRF email blind index evaluation |
POST /auth/opaque/register-start | Registration step 1 |
POST /auth/opaque/register-finish | Registration step 2 |
POST /auth/opaque/authenticate-start | Login step 1 |
POST /auth/opaque/authenticate-finish | Login step 2 |
GET /auth/recovery | Fetch UMK backup |
POST /auth/recovery | Complete account recovery |
POST /auth/tokens/refresh | Token rotation (body-token auth, no session) |
Platform
| Endpoint | Why it's public |
|---|---|
GET /public-keys/server | Clients need the platform public key to wrap DEKs before they have a session |
POST /verifications | Seal and capability token verification must work without a session |
Grants
| Endpoint | Why it's public |
|---|---|
GET /grants | Anonymous grant discovery via view tags — recipients don't need a session to check for incoming grants |
DELETE /grants/{id}/claim | Grantee self-revoke via claim token — authenticates by claim token, not session |
Users
| Endpoint | Why it's public |
|---|---|
GET /users/{userId}/public-keys | Grant senders need to look up recipient public keys without the recipient being online |
All other endpoints require a valid session token.
API Conventions
Schemas and endpoint signatures live in the API Reference. This page covers behavior — how authentication works, how errors are shaped, what headers to expect, and the guarantees the server makes.
Session Lifecycle
Session states, the browser bind protocol, how to unlock a locked session, and how to end one.