Contact EN 中文

Identity Platform

Three-layer identity platform for staff and volunteers on self-hosted Supabase

Proof of concept Solo

No public link. This page describes the problem, decisions and outcome.

At a glance

Problem
At the Tzu Chi Foundation, staff and volunteers sign in to many separate business systems, each with its own accounts.
What I built
An internal proof of concept for a three-layer identity platform.
Outcome
A working internal POC in 19 days, with 38 SQL migrations and 191 TypeScript test files; not in production yet.
My role
Solo
Period
2026-10-01 → 2026-10-19

Abilities shown

Each links to the ability and its level on the Skills page.

The problem

At the Tzu Chi Foundation, staff and volunteers sign in to many separate business systems, each with its own accounts. There was no single answer to “who is this person, and which roles do they hold today”, so access could not be granted, revoked or audited in one place.

What I built

An internal proof of concept for a three-layer identity platform. Layer 1 handles sign-in and issues tokens. Layer 2 is the system of record for people and their organisational roles. Layer 3 is each business system, which connects through a written integration contract and keeps its own data and authorisation. Layers 1 and 2 share one self-hosted Supabase project; I also built the login and consent app, an admin back office, a demo client and the gateway in front of it.

How it works

  1. A business system sends the user to the platform with OAuth 2.1 authorization code + PKCE.
  2. Staff and volunteers press the same Google button. Staff status comes from the hd claim that Google signs into the ID token, never from the email string.
  3. Before a new account is created, a Postgres hook rejects sign-ins that look like staff but lack a signed hd.
  4. When a token is issued, a second Postgres hook reads the governance schema and adds person_id, is_staff, and only the roles and profile fields that this business system was approved to see.
  5. The business system verifies the ES256 token locally against JWKS, with no call back to the platform.

Decisions

  • Supabase Auth over Zitadel. One-time-code login was a hard requirement, and Zitadel only supported it as a second factor.
  • OIDC instead of SAML. The POC showed that GoTrue did not check the email inside a SAML assertion against the provider’s domains, so staff status moved to Google’s signed hd. This avoids the class of bugs where an email string grants access.
  • A platform-issued person_id. OIDC only guarantees that issuer + subject is stable, so business systems store a platform ID that survives a change of identity provider.
  • Hooks run inside the database. Both hooks are SQL functions with indexed lookups, so the login hot path makes no external calls.

Outcome

It is an internal POC/MVP, not in production. It went from start to a working POC in 19 days, with 38 SQL migrations and 191 TypeScript test files. The draft integration contract documents what is not instant: after a ban or session revocation, an already-issued access token stays valid for up to 15 minutes, plus the business system’s clock tolerance. I built it with Claude Code using a spec → plan → implement → review loop. Plans went through design, executability and security review rounds, and Codex also reviewed the specs.