arizuko › components › authd
authd
What it is
In plain terms, authd is the ID office. It checks who you are — through GitHub, Google, Discord, or Telegram — and hands back a signed token that proves it. Every other daemon trusts that token.
authd is the auth plane: the sole platform-token signer and JWKS publisher. It holds the active ES256 (P-256) signing key in memory, mints every access and refresh token, and acts as the OAuth provider. Backends never sign — they fetch authd’s public JWKS and verify tokens locally through the auth/ library. authd owns auth.db and migrates it itself.
One line to keep straight: authd authenticates, routd authorizes. authd answers “who are you, and what is the ceiling of what you could be allowed”; it does not decide whether a given action on a given folder is permitted. That gate lives in routd, the ACL owner.
Why it exists
If every daemon signed its own tokens, every daemon would hold a signing key, and a leak anywhere would be a leak everywhere. Concentrating the key in one daemon means there is exactly one secret to protect and one place to rotate it. authd is that daemon.
It does the login dance: a user signs in with GitHub, Google, Discord, or the Telegram widget, and authd exchanges that successful login for a short-lived ES256 access token plus a refresh token. The access token’s scope is bounded by a snapshot of the subject’s grants — which authd fetches from routd at mint time, because routd owns the ACL. Refresh tokens rotate with reuse detection: replay a spent token and the whole family is revoked.
It also bootstraps the daemons themselves. At boot authd self-mints service:authd, and it exchanges each daemon’s service key for a service:<name> token — so routd, runed, webd, and proxyd all carry an identity authd can verify. Key rotation keeps one active key (enforced by a partial-unique index) while still serving recently-retired keys until their window closes; RevokeAllNow is the emergency cut.
How it fits
browser login (GitHub / Google / Discord / Telegram)
| GET /auth/*
v
authd snapshot grants <-- routd (GRANTS_URL, the ACL owner)
| mint ES256 access + refresh token
v
user / daemon holds a token
every backend (routd / runed / webd / proxyd / dashd / onbod)
| GET /v1/keys (public JWKS, fetched once, cached)
v
auth.VerifyToken verifies offline, no call back to authd per request
Inputs: OAuth callbacks on /auth/*; mint and downscope requests on POST /v1/tokens; daemon service-key exchange on POST /v1/service-token; refresh rotation on POST /v1/refresh; the grants ceiling fetched from routd. Outputs: ES256 tokens; the public JWKS at GET /v1/keys that every backend reads to verify offline.
Hard deps: its own auth.db (direct SQLite, no sibling DB). Soft dep: routd via GRANTS_URL for the login-time scope ceiling — unset, sessions come back empty-scope. authd is not the grants authority; it reads the ceiling, it does not set it.
Verify offline, mint in one place
The split is deliberate: minting is centralized (one key, one daemon), verification is distributed (every backend, no round-trip). A request carrying a token never calls back to authd — the backend already has the JWKS and checks the signature locally. authd can be momentarily down and existing tokens still verify; only new logins and refreshes block.
Standalone usage
authd is the most standalone of the split daemons — it owns its DB and its signing key, and it needs no other daemon to mint and verify tokens. The one soft tie is GRANTS_URL: without routd, it still issues tokens, but their scope ceiling is empty.
cd /srv/data/myinstance
export DATA_DIR=/srv/data/myinstance # resolves <dir>/store/auth.db
export LISTEN_ADDR=:8080
export AUTHD_SERVICE_KEY=$(grep ^AUTHD_SERVICE_KEY .env | cut -d= -f2)
export AUTHD_SERVICE_KEYS='routd=...,runed=...,webd=...,proxyd=...'
export GRANTS_URL=http://routd:8080
# OAuth provider config, mount /auth/* only when present:
export GITHUB_CLIENT_ID=... GITHUB_CLIENT_SECRET=...
export AUTH_BASE_URL=https://example.com
./authd
The /auth/* routes mount only when provider config is present. GET /health returns 200 once the signer is live with at least one serving key.
What authd does not do
authd does not authorize. It mints and verifies, runs the OAuth dance, and rotates keys — but it never decides whether a caller may touch a particular folder. That decision is routd’s, every time.
Go deeper
- concepts/auth — the authentication model: providers, tokens, the JWKS verify path.
- concepts/scopes — what a scope is and how the grants ceiling bounds a token.
- reference/env — split daemons —
AUTHD_URL,AUTHD_SERVICE_KEY(S),GRANTS_URLand the rest of the wiring. - components/routd — the authorization authority authd defers to.
authd/README.md— mint surface, key rotation, file map.specs/5/1— the auth plane split.