Skip to content
Case study · MattediWorks ecosystem

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.

trace · rotate a Mail project keylive replay
  1. browserPOST /api/mail/projects/7/rotate-key
  2. cookie mw_admin_session=••••• HttpOnly · SameSite=Strict
  3. X-MW-CSRF: ••••• no Authorization header
  4. gatewaystrip /api · X-Request-ID req_7f3a91c2
  5. bffroute POST /mail/projects/{id}/rotate-key
  6. scope mail.credentials.rotate passkey 2 min ago ✓
  7. identitytoken-exchange aud=mw-mail opaque · ttl 5m
  8. mailintrospect as mw-mail ✓ sub=usr_01JB6Q…
  9. mailkey rotated · audit actor usr_01JB6Q…
  10. 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

two trust paths, never mixed

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.

01 / 07
Web Admincookie onlyGatewaynginxMW BFFsession · CSRFMW IdentityOIDC · passkeyMW OAuthaud mw-oauthMW Mailaud mw-mailMQTT adminaud mw-mqtt

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

identity.mattediworks.com

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:

01

No tokens in the browser

Neither Identity access tokens nor delegated tokens ever reach browser code.

02

Audience-bound delegation

A token minted for mw-mail is useless anywhere else, and lives five minutes.

03

Introspect your own audience

A resource server may only introspect tokens whose audience is its own client ID.

04

Fail closed

If introspection fails or Identity is down, the request is refused.

05

Explicit facades only

The BFF is never a generic proxy. Every route names its method, path and scope.

06

Passkeys for sensitive actions

Rotations, deletes and role changes need fresh WebAuthn. TOTP cannot satisfy it.

07

Logic stays in its service

Identity says who you are. Each service decides what that person may do.

08

Keyrings outside backups

Database snapshots never contain the keys that decrypt them.

BFF → Identity · token exchange
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.

admin.mattediworks.com/
Overview screen of the MattediWorks admin console, dark theme
Overview. Each module card is live: it is online only when its downstream service answers through the BFF.

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.

  1. Step 01

    Runtime skeleton

    Gateway, BFF and Identity answering /health behind one ingress.

  2. Step 02

    Local accounts

    Identity users and an operator-bootstrapped superadmin.

  3. Step 03

    Signing and registry

    Encrypted RSA keys, discovery, JWKS and the application registry.

  4. Step 04

    Code + PKCE

    S256 only, exact redirect URIs, one-time hashed codes.

  5. Step 05

    BFF sign-in

    The console signs in through the BFF and gets a cookie, not a token.

  6. Step 06

    Identity admin

    Applications, users, assignments and audit from the console.

  7. Step 07

    Passkeys

    Discoverable WebAuthn credentials with user verification required.

  8. Step 08

    Account linking

    Existing apps map issuer + sub to their own users.

  9. Step 09

    Delegated tokens

    Token exchange for narrow, audience-bound downstream tokens.

  10. Step 10

    OAuth federation

    MW OAuth accepts delegated tokens on its admin API.

  11. Step 11

    Mail federation

    MW Mail does the same, with immutable deletion audit.

  12. Step 12

    Strong auth

    TOTP fallback, explicit WebAuthn step-up, operator recovery.

  13. Step 13v1.0 complete

    Hardening

    Readiness, safe logging, backups, read-only non-root containers.

  14. v1.1

    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.