TessariDB OAuth
Get started

Architecture

One binary, two listeners, one store

The server is one Rust binary. It serves the issuers, the sign-in pages and the account portal on its public listener and the admin console on a listener of its own, and it keeps nothing that matters anywhere but in TessariDB.

Listeners

The public side and the console apart

Either listener speaks TLS when given a certificate, or plain HTTP for a TLS terminator in front of it. The console signs operators in through the company's own issuer, as a client of it.

The two listeners
ListenerDefaultServes
public0.0.0.0:8443discovery, key sets, authorization, token, revocation, UserInfo, logout, the sign-in and consent pages, the account portal
admin0.0.0.0:9443the admin console at <ADMIN_URL>/admin/<company>/, at an origin other than the public one

The store

Every piece of state is a TessariDB record

A region's companies live in the namespace oauth_<REGION>, one database per company, beside the region's directory. Codes, grants, consents, refresh families, sessions, sealed signing keys, replay and throttle counters and the audit trail are that database's records, transactions and spaces.

A code is stored by its SHA-256 digest only and spent with one conditional write, which is what makes one redemption win among any number of simultaneous ones.

  • At start the server records each configured company in the directory, provisions its database and makes it active.
  • It makes a signing key for any company without one, then checks every five minutes for keys due to rotate.
  • The audit trail is a TessariDB topic kept for 400 days.

Keys

Two files the store never sees

Signing keys and other secrets are sealed under a 32-byte key-encryption key; passwords are hashed with Argon2id under a pepper. Both are files on the server, readable by its own user only, so neither is in a backup of the store. reseal moves every sealed secret to a new key-encryption key.

Regions

One home region per company

Each region is its own TessariDB deployment. A server given its peers reads every other region's directory every five seconds and answers a request for a company another region serves with 307 Temporary Redirect to the same path there, so identity data is never proxied across regions.

acme → home region euredirected, never proxied
  • region usGET id.us.example.com/acme/oauth2/authorize?…307 → eu
  • region euGET id.eu.example.com/acme/oauth2/authorize?…answered where acme lives
each region is its own TessariDB store; peers read each other’s directory every five seconds

Sender-constrained tokens

A token that only its key can use

With DPoP (RFC 9449) the client proves possession of a key on every request, and the token carries that key's thumbprint (RFC 7638). A proof is spent once, and a bound token presented as a bearer token is refused.

cnf.jkt = thumbprint(client key)sender-constrained
  • client, with its keyAuthorization: DPoP eyJ… + DPoP: proof(ath, htm, htu)answered
  • someone who copied the tokenAuthorization: DPoP eyJ… + no proof from the key401 invalid_token
  • the same token as a bearer tokenAuthorization: Bearer eyJ…refused
a proof is spent once; keys are named by their JWK thumbprint (RFC 7638)