Add SSO+2FA and outbound-exposure posts with Excalidraw architecture diagrams; Diagram component serves outlined SVGs
build-and-deploy / build (push) Failing after 12m1s
build-and-deploy / build (push) Failing after 12m1s
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
---
|
||||
title: "SSO is a perimeter decision, not a login box"
|
||||
date: 2026-06-23
|
||||
summary: "Putting single sign-on in front of a fleet of self-hosted apps isn't about a nicer login screen — it's about where your trust boundary lives, and how it fails."
|
||||
tags: ["authentik", "oidc", "sso", "security", "kubernetes"]
|
||||
draft: false
|
||||
---
|
||||
|
||||
Most write-ups treat single sign-on as a feature you bolt on: stand up an identity provider, point the
|
||||
apps at it, enjoy one login. That framing misses the actual decision. SSO *moves your trust boundary* —
|
||||
every app now trusts one issuer, and every account is one credential away from all of them. Get it right
|
||||
and you've hardened the whole estate at once. Get it wrong and you've built a single, central thing to lose.
|
||||
|
||||
Here's how I think about it after wiring an OIDC provider in front of a self-hosted fleet.
|
||||
|
||||

|
||||
|
||||
## Enforce the second factor at the flow, not per app
|
||||
|
||||
The leverage is putting 2FA on the *authentication flow itself*, not on each application. Every app that
|
||||
federates to the provider inherits it for free — you can't forget to enable 2FA on app number nine, because
|
||||
app number nine never sees a password. Enrolment (TOTP / WebAuthn) is mandatory at the identity layer, and
|
||||
the apps just receive an already-verified identity.
|
||||
|
||||
That's the whole point of centralising: you set the policy once, at the boundary, and everything downstream
|
||||
gets it.
|
||||
|
||||
## Keep a key under the mat
|
||||
|
||||
The flip side of "one issuer for everything" is that when the issuer is down — or you fat-finger the auth
|
||||
flow — you can lock yourself out of *everything*, including the tools you'd use to fix it. So every critical
|
||||
app keeps a **local break-glass admin** that bypasses SSO, and the recovery codes for 2FA enrolment live
|
||||
offline.
|
||||
|
||||
SSO is the front door. Break-glass is the fire exit. You don't ship a building with only one.
|
||||
|
||||
## The gotchas that actually cost time
|
||||
|
||||
Two traps ate real hours, and they're the same lesson wearing two hats:
|
||||
|
||||
- **A baked-in config file silently overrode environment variables.** I set the OIDC scopes via env, login
|
||||
kept failing with *"missing fields: email"*, and the cause was a config file winning over the env. The
|
||||
provider needs `openid email profile` explicitly — set where the app actually reads it, not where you
|
||||
assume it does.
|
||||
- **In-cluster back-channels can't use the public hostname.** The browser hits the public login URL fine,
|
||||
but the app's *server-side* token exchange runs inside the cluster, where split-horizon DNS doesn't
|
||||
resolve the public name. The fix: point the back channel at the internal service address while the
|
||||
browser-facing URL stays public.
|
||||
|
||||
Both say the same thing: SSO has two channels — the one the user sees and the one the server uses — and they
|
||||
don't live on the same network.
|
||||
|
||||
## The principle
|
||||
|
||||
Centralising identity is a force multiplier, but it concentrates risk *by design*. So design the
|
||||
concentration: enforce the strong factor at the boundary, plan the failure mode before you need it, and
|
||||
remember that OIDC authenticates a *user* — it doesn't, on its own, make a sensitive service safe to expose.
|
||||
The login box is the easy part. The perimeter is the decision.
|
||||
Reference in New Issue
Block a user