RFC-002 — Identity-Aware Proxy: Koder ID SSO at the Koder Jet edge

Author: Rodrigo (with Claude as scribe) Date: 2026-05-30 Status: Accepted (2026-05-30, owner-approved). Implementation in progress — the capability ships opt-in (mode="sso", no vhost flipped); the global default-flip remains gated on the homologação signal (§7). Modules:

  • infra/net/jet (the edge proxy — enforces auth)
  • services/foundation/id (IdP: OAuth2/OIDC, sessions, the dev-access claim)

Origin: ad-hoc conversation 2026-05-30, after the jet#188 post-mortem (the auth-gate email-approval channel was found broken AND structurally noisy). Anchored on policies/environments.kmd § "Lifecycle of the email-approval gate" (which already names "pure Koder ID auth" as the successor) and specs/auth/oauth-flow.kmd.


1. Summary

Replace email-approval-per-IP with an Identity-Aware Proxy (IAP) as the primary access-control mechanism for environment = dev | staging vhosts. Instead of allowlisting IPs after an out-of-band email approval, Koder Jet enforces a valid Koder ID session at the edge, before the request reaches the app. This is the BeyondCorp / Google IAP / Cloudflare Access / Pomerium / oauth2-proxy pattern, built from machinery the Stack already owns: Koder ID (OAuth2/OIDC + PKCE) as the IdP and Koder Jet (which gained an OAuth client + Bearer introspection in jet#188) as the proxy.

The email-approval gate is not deleted — it is demoted to a narrow guest-pass for external visitors without a Koder ID account.

2. Problem (why the email gate is structurally wrong as the default)

The email-approval-per-IP model has three defects that no amount of tuning fixes — they are inherent to the design:

  1. Noise scales with crawler traffic. dev/staging vhosts are publicly

    resolvable + TLS-terminated, so the entire internet reaches them. The gate does work (enqueue pending, send email/push) for every hostile or automated hit. The jet#188 post-mortem: bots (bingbot, OAI-SearchBot, Censys, …) sweeping ~20+ gated *.koder.dev hosts exhausted the shared Stalwart (sender_domain, rcpt) rate bucket, so the owner's own approval mail was dropped — and the owner approved the wrong host's email instead. jet#190 (bot-UA suppression) and the Stalwart rate raise treat the symptom; they don't remove the class of problem.

  1. IP allowlisting is brittle for the legitimate user. Mobile carrier

    IPs rotate; the owner re-approves constantly, and permanent_whitelist with a rotating /32 is a band-aid (observed live, 2026-05-30).

  1. Weak authorization (jet#189). The confirm callback accepts any

    active Koder ID session and never binds the approver to the vhost's admin. There is no concept of "who is allowed into dev".

3. Goals / Non-goals

Goals

  • A team member with a Koder ID reaches any authorized dev/staging host

    with zero per-host friction — log in once, SSO across all *.koder.dev.

  • Bots/scanners get a login redirect and generate zero notifications.
  • Authorization is identity/group based, not IP based.
  • Reuse the jet#188 OAuth-client + introspection machinery; no new IdP.

Non-goals

  • Replacing the VPN tier (truly-internal services stay VPN-only, per

    environments.kmd line 43 — "VPN-only internal services not bound to public IPs").

  • Deleting the email/guest-pass flow.
  • Changing prd behavior (prd vhosts are never gated).

4. Design

4.1 Request flow (the IAP path)

request → *.koder.dev (dev/staging)
  │
  ├─ has valid jet session cookie?  ── yes ──► authz check (4.3) ──► app
  │                                   no
  ▼
302 → https://id.koder.dev/oauth/v2/authorize
        ?client_id=koder-jet-iap & redirect_uri=https://<host>/__koder_auth__/callback
        & response_type=code & scope=openid profile email & PKCE(S256) & state
  │
  ▼  (user authenticates at Koder ID — never an embedded form, per oauth-flow.kmd R6)
302 → https://<host>/__koder_auth__/callback?code&state
  │
  ▼  jet: exchange code (PKCE) → id_token+access_token; validate; resolve dev-access (4.3)
Set-Cookie: koder_edge_sess=<jwt>; Domain=.koder.dev; Secure; HttpOnly; SameSite=Lax
302 → original URL

After the first login, the .koder.dev-scoped cookie means every dev host is reached without another round-trip (SSO).

4.2 Session

  • Cookie: koder_edge_sess, `Domain.koder.dev; Path/; Secure;

    HttpOnly; SameSite=Lax (per oauth-flow.kmd` R8).

  • Value: a short-lived JWT signed by jet (HS/EdDSA, key in jet's

    env), carrying sub, email, dev_access, exp. jet validates it locally on every request — no introspection on the hot path.

  • Refresh: jet keeps the Koder ID refresh_token server-side (keyed by

    an opaque session id in the cookie, OR encrypted in the cookie) and re-mints the edge JWT when it nears expiry. TTLs per oauth-flow.kmd R8 (access 15m, refresh 180d, idle 90d). For an MVP, edge JWT exp = 12h with silent re-auth is acceptable.

  • Callback path: `/_oderauth_