One identity.
Every service.
Zero tokens in the browser.
An OpenID provider, a backend-for-frontend admin console and three federated services: OAuth, Mail and MQTT. Written in Go and React, deployed through Stackport, and designed so that every admin action names the person who took it.
- browserPOST /api/mail/projects/7/rotate-key
- cookie mw_admin_session=••••• HttpOnly · SameSite=Strict
- X-MW-CSRF: ••••• no Authorization header
- gatewaystrip /api · X-Request-ID req_7f3a91c2
- bffroute POST /mail/projects/{id}/rotate-key
- scope mail.credentials.rotate passkey 2 min ago ✓
- identitytoken-exchange aud=mw-mail opaque · ttl 5m
- mailintrospect as mw-mail ✓ sub=usr_01JB6Q…
- mailkey rotated · audit actor usr_01JB6Q…
- 200OK bearer tokens seen by the browser: 0
- Go services
- 4
- Identity, BFF, OAuth and Mail
- lines of Go
- 42k
- excluding tests
- automated tests
- 311
- 179 Go test functions, 132 frontend
- security invariants
- 21
- written down and enforced
01The problem
Every app had its own admin login. Now none of them do.
MW Mail and MW OAuth each started with a local admin password. As more services arrived, the goal became one trust boundary for people, without touching how apps already talk to those services.
Prove who you are in one place
MW Identity owns people, passkeys and sessions. Apps keep their own user tables and link them to a stable issuer + sub, so “Sign in with MattediWorks” works without moving anyone’s data.
Keep business logic at home
Identity answers “who is this?”. Each service answers “may they do this?” with its own scopes. Identity never becomes a central copy of every service’s rules.
Infrastructure is another plane
Stackport, the VPS manager, sits outside this trust boundary. Compromising the admin console must not hand out Docker, nginx, firewall or host control.
People
Web Admin → Gateway → BFF → Identity / service
Applications
App backend → service public API with its own mw_live_… key
02Architecture
Follow a request through the control plane.
The browser talks only to the BFF. The BFF never forwards its own Identity token: for each admin call it exchanges it for a five-minute token that only one service will accept, carrying only the scope that one route needs.
Rotating a Mail key. The service sees the human who asked, with only the scope that route needs.
03Services
Six parts, each with one job.
Every component owns a narrow slice of the problem and refuses the rest. Identity knows nothing about mail quotas; MW Mail knows nothing about passkeys.
MW Identity
OpenID Provider · Authorization Server
The single source of truth for who a person is. It issues every token in the ecosystem and is the only service that ever sees a password or a passkey.
Owns
- Users, sessions and the stable human sub
- OIDC discovery, JWKS and Authorization Code + PKCE
- Application registry and assignments
- WebAuthn passkeys, TOTP fallback, operator recovery
- Token exchange and introspection
- Signing keys, revocation and security audit
- ID tokens
- RS256, 5 min
- Delegated tokens
- opaque, 5 min
- Keys at rest
- AES-256-GCM keyring
- Go
- 15.3k lines · 11 migrations
04Security model
Twenty-one rules the code has to keep.
The control plane documents its security invariants and tests against them: wrong audience, missing scope, Identity outage and unmapped routes all have end-to-end checks. A few of them:
No tokens in the browser
Neither Identity access tokens nor delegated tokens ever reach browser code.
Audience-bound delegation
A token minted for mw-mail is useless anywhere else, and lives five minutes.
Introspect your own audience
A resource server may only introspect tokens whose audience is its own client ID.
Fail closed
If introspection fails or Identity is down, the request is refused.
Explicit facades only
The BFF is never a generic proxy. Every route names its method, path and scope.
Passkeys for sensitive actions
Rotations, deletes and role changes need fresh WebAuthn. TOTP cannot satisfy it.
Logic stays in its service
Identity says who you are. Each service decides what that person may do.
Keyrings outside backups
Database snapshots never contain the keys that decrypt them.
POST /oauth/token grant_type=urn:ietf:params:oauth:grant-type:token-exchange subject_token=<BFF-held Identity token, decrypted in memory> audience=mw-mail scope=mail.credentials.rotate # before any of that, a sensitive route checks the passkey is under 5 min old. # if not, the BFF answers the browser: 403 { "error": { "code": "step_up_required", "step_up_url": "/api/auth/step-up" } }
Never written to a log
Access logs record the path only, never the query string. Audit and diagnostics are kept apart.
05The console
One console for every service.
MW Web Admin replaced the separate admin UIs that Mail and OAuth used to ship. Same session, same step-up, same audit trail, in English or Portuguese, light or dark.
Captured locally: the real console served against a mock BFF with fictional data, and the real Identity server.
06Process
Thirteen gated steps, then one more.
The control plane was built from August to October 2026 under one rule from its roadmap: Do not begin the next step while the current step has failing tests or an unexplained runtime result.
Every step had goals, automated tests, runtime checks and, where it applied, an end-to-end checkpoint.
Runtime skeleton
Gateway, BFF and Identity answering /health behind one ingress.
Local accounts
Identity users and an operator-bootstrapped superadmin.
Signing and registry
Encrypted RSA keys, discovery, JWKS and the application registry.
Code + PKCE
S256 only, exact redirect URIs, one-time hashed codes.
BFF sign-in
The console signs in through the BFF and gets a cookie, not a token.
Identity admin
Applications, users, assignments and audit from the console.
Passkeys
Discoverable WebAuthn credentials with user verification required.
Account linking
Existing apps map issuer + sub to their own users.
Delegated tokens
Token exchange for narrow, audience-bound downstream tokens.
OAuth federation
MW OAuth accepts delegated tokens on its admin API.
Mail federation
MW Mail does the same, with immutable deletion audit.
Strong auth
TOTP fallback, explicit WebAuthn step-up, operator recovery.
Hardening
Readiness, safe logging, backups, read-only non-root containers.
MQTT module
A third delegated service, opt-in, with no v1.0 contract touched.
07Operations
Run like production, on a small footprint.
Each service is a single static Go binary (CGO disabled) with its own SQLite database in WAL mode. No message queue, no shared database, no web framework. Each of the thirteen control-plane steps has its own sanity and end-to-end script.
No published ports
Managed deployments never publish a host port. The gateway listens on container port 8080 behind Stackport’s proxy network, and TLS ends at the outer nginx.
Locked-down containers
Non-root users, read-only root filesystems, every Linux capability dropped, no-new-privileges and small bounded tmpfs mounts.
Backups that check themselves
Control-plane backups are online SQLite snapshots with a SHA-256 manifest. Keyrings never go in the archive, and a restore can run in validate-only mode first.
Health is not readiness
/health says the process is alive. /ready says it can do real work, database included. Orchestration uses the right one.
Recovery without email
There is no email reset. An operator CLI revokes every session and token, then issues a short-lived one-time capability stored only as a hash.
Services
- Go 1.26
- net/http, no framework
- ≤ 6 direct dependencies each
- SQLite · modernc · WAL
- go-webauthn
- log/slog JSON logs
Console
- React 19
- Vite 8
- TanStack Query
- Tailwind 4 · Radix
- Vitest · Testing Library
Protocols
- OpenID Connect
- OAuth 2.0 + PKCE S256
- RFC 8693 token exchange
- RFC 7662 introspection
- WebAuthn · TOTP
Operations
- Docker Compose
- nginx 1.29
- Stackport
- Online SQLite snapshots
- SHA-256 manifests
Every layer, built in-house.
MattediWorks designs, builds and runs software like this end to end: protocols, services, interfaces and the infrastructure under them.
