Forward
One identity provider, every service, and the configuration that makes it work.
This book is a working reference for putting every service in a self-hosted environment behind a single identity provider. It opens with the case for why you should centralize your identities and the reasoning behind the tooling, then gives annotated, reproducible configuration for three representative integration patterns: an identity-only app (Discourse), a group-aware app (BookStack), and an app you write yourself (a Flask service). Each configuration page calls out the pattern it represents so it can be adapted to similar applications.
Why centralize identity
Every application that manages its own logins is a small identity system you now have to run. It stores passwords, it decides how (or whether) to do multi-factor authentication, it has its own idea of what a session is, and it keeps its own list of who is allowed in. Run a handful of apps that way and you have a handful of password stores, a handful of MFA configurations of varying quality, and a handful of places you have to remember to visit when someone should no longer have access.
A single sign-on identity provider (IdP) collapses all of that into one place. Users authenticate once, against one system, and every application trusts that system instead of holding credentials of its own. The practical consequences are the whole point:
- One place to authenticate. One login, one session, one password users actually have to remember, which means it can be a strong one, backed by MFA, instead of ten mediocre ones.
- One place to enforce policy. MFA, password rules, and session lifetimes are configured once and apply everywhere. No app can quietly be the weak link.
- One place to revoke. Offboarding is a single action. Disable the account at the IdP and every downstream application is closed at once, with no hunting through ten admin panels hoping you didn't miss one.
- Apps stop storing passwords. Each application holds a client secret and a trust relationship, not user credentials. The blast radius of any single app being compromised shrinks accordingly.
This matters more at small scale, not less. A large organization has an IAM team to manage per-application accounts. One person running an environment for friends and family does not, and cannot manually keep account state consistent and secure across every service by hand. Centralizing identity is what makes right-sized, genuinely secure operation achievable by one person. It is the single highest-leverage security decision in the whole environment.
There is a catch that is easy to underrate: for any of this to hold, every service has to sit behind the IdP. A single application with its own local-login escape hatch defeats the revocation guarantee, the MFA guarantee, and the "apps don't store passwords" guarantee all at once. Universality isn't a nice-to-have here. It is the property that makes the model worth anything.
Why Keycloak
The IdP for this environment is Keycloak. The reasons it won out:
- Open-source and self-hostable. It runs on infrastructure I control, with no per-seat SaaS pricing and no user identities living in someone else's cloud. For a privacy-focused, self-hosted environment, that alignment matters; the identity system shouldn't be the one component you rent from a third party.
- Standards-based. It speaks OIDC, OAuth 2.0, and SAML rather than a proprietary protocol. Every integration in this book is a standard OIDC flow, which means the knowledge transfers directly to enterprise systems and there is no lock-in to a vendor's dialect.
- Realms give real isolation. Keycloak's realm model allows entirely separate identity domains. Production services authenticate against one realm; development instances authenticate against a second. Dev accounts and prod accounts never mingle, and client and mapper configuration can be tested in dev without risking the identity system real people depend on.
- Enough authorization power without the bloat. Groups, roles, client scopes, and protocol mappers are enough to drive real access control in every downstream app, without dragging in a heavyweight enterprise IAM suite that would be absurd at this scale.
The honest tradeoff: Keycloak has a real learning curve, and it does not shy away from breaking changes between major versions. Upgrades are planned maintenance windows with database backups taken first, release notes read carefully, and realm and client configuration re-verified afterward, not a casual docker compose pull. That is a cost taken on with eyes open. The centralization it buys is worth the operational care it demands.
How it fits the architecture
The IdP lives in the environment's infrastructure and trust zone, on its own host, and like everything else it is reachable only from behind the edge. Applications never handle credentials themselves. When a user hits an application without a valid session, the app redirects the browser to Keycloak; the user authenticates there (with MFA where required); Keycloak returns an authorization code; and the application exchanges that code for tokens on a back channel it never exposes to the browser. Authorization is then decided by the application from the claims in the token it receives.
Every integration uses the OIDC authorization-code flow with PKCE (S256), confidential clients, and standard flow only. That uniformity is deliberate: a new service isn't a fresh design problem, it is the same pattern applied again, which is what keeps the whole thing maintainable by one person.
The pattern every service follows
Onboarding a new application is a checklist, not an improvisation:
- Create a confidential client in the appropriate realm, standard flow only, PKCE S256.
- Set the valid redirect URI to the application's callback, and the post-logout redirect back to the app.
- Wire the application's OIDC settings to the realm's discovery document, so endpoints are resolved automatically:
https://auth.example.org/realms/landisfam/.well-known/openid-configuration
For a simple service that only needs to know who the user is, that is the entire integration: scopes of openid email profile, match the user by email, done. The moment an application needs to know not just who you are but what you are allowed to do, group and role information has to travel from Keycloak into the app, and that is where the standards-based simplicity meets a dozen different implementations of the same idea.
The principle that governs every integration below: configure the identity provider to the consumer's requirements, not its own defaults, and confirm the claim reaches the exact token the application reads before trusting the integration. "It's in Keycloak" is not the same as "the app can see it." Every gotcha in this book is a variation on that one theme.
The three pages that follow are the three patterns:
- Discourse — identity only. Use for any app that just needs to authenticate a user.
- BookStack — group-aware. Use for any app that maps IdP groups onto internal roles.
- Flask — build it yourself. Use when the app is your own code and you own the OIDC client.