
Identity
Self-Hosted SSO in 2026: Keycloak vs Authentik vs Zitadel After Okta
Own your identity plane: a trust-model decision matrix comparing Keycloak, Authentik, and Zitadel as self-hosted SSO — the engineering exit from Okta.
Most engineering organizations can tell you exactly where their production data lives and who can compel access to it. Ask the same question about the *login* — the single front door every employee and customer passes through to reach anything — and the answer is usually a vendor's name. Identity is the most load-bearing dependency in the building: it decides who is allowed to do what, and it has quietly become the one component almost nobody owns. The plane is rented, the bill is metered per human, and the blast radius of its failure is every system behind it.
This is not an anti-Okta polemic. Okta and Auth0 are excellent products, and for many teams a hosted identity provider remains the right call. But "correct for now" and "something you could never leave" are different postures, and the distance between them is exactly the optionality this practice exists to defend. This article is the concrete version of that argument for the human identity plane: a complete exit built from owned, open-source providers — Keycloak, Authentik, and Zitadel — chosen not by feature count but by trust model, and wired into an SSO layer you operate, audit, and can replace one protocol at a time.
It is deliberately the human-identity complement to our work on machine identity. The SPIFFE/SPIRE workload identity playbook is about how *services* prove who they are to each other — attested, secret-free, cryptographic identity for code. This piece is about how *people* prove who they are to your applications: sessions, multi-factor, consent, federation, and the deprovisioning that has to happen the hour someone leaves. The two planes meet, but they are governed by different tools and different failure modes, and conflating them is how identity programs go wrong.
The Year Identity Got a Price Tag
The case for owning identity used to be mostly philosophical. Three forces in the last few years turned it operational: the economics, the breaches, and the jurisdiction. None is a reason to panic. Together they are a reason to have an exit ramp — a different and more durable thing.
The economics came first. Okta acquired Auth0 in 2021 for $6.5 billion, and the dominant billing model for customer identity is per monthly active user — every unique human who authenticates at least once in a billing period. That meter has a direction. In its November 2023 pricing change, Auth0's B2C overage rate moved from $0.023 to $0.07 per MAU — roughly a threefold jump — while base tiers were re-cut to fewer included users. Teams routinely report renewal quotes several times their prior bill as user counts grow, and capabilities a serious deployment needs — SAML, advanced MFA, lifecycle governance — sit behind higher tiers and custom quotes. The number on the invoice is set by someone else's strategy, and it changes on their schedule, not yours.
Then jurisdiction. In July 2020 the EU Court of Justice invalidated the Privacy Shield in *Schrems II*, on the grounds that US surveillance law is incompatible with EU data-protection guarantees. Its successor, the EU-US Data Privacy Framework, is itself under live legal challenge — the General Court dismissed the first case in September 2025, and an appeal to the CJEU was accepted that November. For a European company, routing every employee and customer authentication — and the personal data inside those tokens — through a US-headquartered identity vendor is now a standing compliance question, not a settled one. That pressure is why "European alternatives to Okta" became its own procurement category in 2026, and it stacks directly on the NIS2 and DORA obligations that name identity providers as critical dependencies an audited entity must be able to control and evidence.
Human Identity Is Not Machine Identity
Before choosing a tool, be precise about which problem it solves, because the identity word covers two planes that look similar and behave nothing alike. Workload identity is how a service proves itself to another: a SPIFFE SVID issued after node and workload attestation, short-lived, auto-rotated, with no human in the loop and no password anywhere — the SPIFFE/SPIRE plane, where SPIRE, mTLS, and X.509 are the right tools.
Human identity is a different animal. A person authenticates interactively, holds a session that outlives a single request, carries factors that can be phished and so need MFA or passkeys, grants consent, and — critically — must be *deprovisioned* the moment they leave, across every downstream app at once. The protocol surface is OIDC, OAuth 2.0, SAML, and SCIM, not SVIDs and node attestation. Keycloak, Authentik, and Zitadel live entirely on this human plane; none of them replaces SPIRE, and SPIRE does not replace them. The mature posture runs both — cryptographic identity for workloads, a sovereign SSO provider for people — with a clear seam where a human's token authorizes an action a workload then carries out. Pick the wrong plane's tooling and you will spend a quarter discovering it.
What an Identity Provider Actually Owns
An identity provider is small in concept and enormous in consequence. It owns the user store — the directory of accounts, organized as realms, organizations, or tenants. It owns the session and tokens minted from it, the factors that gate them (TOTP, WebAuthn/passkeys, recovery flows), the federation to upstream sources (Active Directory, Google, Microsoft Entra), and the provisioning that creates and destroys accounts downstream. Lose control of any of these and you have lost control of who can do what.
Owning it is even feasible only because every seam is a standard protocol. OIDC is an authentication layer on top of OAuth 2.0 — OAuth alone authorizes access and says nothing about *who* the user is; OIDC adds the identity in a signed ID token and a discovery document at a well-known URL. SAML 2.0 is the older XML protocol enterprise and legacy apps still speak; SCIM (RFC 7643/7644) provisions and de-provisions accounts; and the emerging OAuth 2.1 consolidation makes PKCE mandatory and drops the implicit flow. Because these contracts are open, an app consuming standard OIDC neither knows nor cares which IdP issued its token — which is what makes the provider behind the seam replaceable.
Keycloak: The Incumbent Standard
Keycloak is the default answer for a reason: open since 2013, it speaks essentially everything, and in April 2023 it joined the CNCF as an incubating project, giving it neutral governance to match its reach. The modern distribution is a real change from the Keycloak many teams remember: the old WildFly server is gone — the Quarkus distribution became default in Keycloak 17 and WildFly was removed entirely in Keycloak 23 — so the 26.x line starts faster, uses less memory, and behaves like a container-native service. Red Hat ships a supported build (RHBK) for teams wanting a vendor SLA behind the same codebase.
Architecturally, Keycloak organizes the world into realms: each realm is an isolated universe of users, clients, roles, and identity-brokering config, administered from a master realm. It requires an external relational database — PostgreSQL is the recommended store, with no production-grade embedded option — and it clusters for high availability, with database-based node discovery (JDBC-Ping) the default since 26.1, removing the old reliance on UDP multicast. An official Kubernetes operator manages the deployment. The trade is honest: you get the broadest protocol and brokering support of the three and a colossal community, at the cost of real operational weight — a database to run, a cluster to coordinate, and a configuration surface deep enough to need a specialist. The realm model is also an awkward fit for true B2B multi-tenancy, where each customer wants its own isolated identity space.
# Keycloak 26 (Quarkus): register an OIDC client for a downstream app.
# kcadm talks to the admin REST API — the same surface the operator drives.
kcadm.sh config credentials \
--server https://idp.example.internal \
--realm master --user "$ADMIN" --password "$ADMIN_PW"
# Create a realm dedicated to the workforce, then a confidential OIDC client.
kcadm.sh create realms -s realm=workforce -s enabled=true
kcadm.sh create clients -r workforce \
-s clientId=internal-dashboard \
-s protocol=openid-connect \
-s publicClient=false \
-s standardFlowEnabled=true \
-s 'redirectUris=["https://dashboard.example.internal/oauth2/callback"]' \
-s 'attributes={"pkce.code.challenge.method":"S256"}'
# The app now trusts one issuer you operate:
# https://idp.example.internal/realms/workforce/.well-known/openid-configurationAuthentik: The Application-Centric Challenger
Authentik, built by the public-benefit company Authentik Security, approaches the problem from the application side rather than the protocol side, and that reframing is its whole appeal. Its core is a Django application; its distinctive move is a fleet of stateless Go-based **outposts** — a proxy outpost that does forward authentication for apps that speak no SSO at all, an LDAP outpost for legacy LDAP clients, and a RADIUS outpost for network gear. The proxy outpost is the headline: it puts a real authentication gate in front of an internal tool that was never designed for one — the exact situation most organizations are actually in.
Authentik models authentication as flows composed of stages — a directed graph where each stage (identify, password, TOTP, WebAuthn, consent, enrollment, recovery) is a unit you can reorder, branch, or customize, in a visual editor or as YAML blueprints you commit to git. It supports OIDC, SAML, LDAP, RADIUS, and SCIM provisioning, and has been steadily closing enterprise gaps: single logout arrived in 2025.10, passkey autofill (WebAuthn conditional UI) in 2025.12. Operationally it has been getting *lighter* — the 2025.10 release removed the Redis dependency, folding caching and task queues into PostgreSQL, so a production deployment is now Authentik plus one database. It is the natural choice when your reality is a sprawl of heterogeneous apps — some modern, many not — that all need to sit behind one login.
# Authentik's proxy outpost gating an app that speaks no SSO.
# nginx forwards every request to the outpost's /auth/nginx endpoint;
# only an authenticated session is allowed through to the upstream.
location / {
auth_request /outpost.goauthentik.io/auth/nginx;
error_page 401 = @goauthentik_proxy_signin;
# Pass identity headers from the outpost to the protected app.
auth_request_set $authentik_username $upstream_http_x_authentik_username;
proxy_set_header X-authentik-username $authentik_username;
proxy_pass http://legacy-internal-tool:8080;
}
location /outpost.goauthentik.io {
proxy_pass http://authentik-proxy-outpost:9000/outpost.goauthentik.io;
proxy_set_header X-Original-URL $scheme://$http_host$request_uri;
}Zitadel: Multi-Tenant and API-First
Zitadel, from the Swiss company CAOS in St. Gallen, is built for a specific shape of problem: multi-tenant SaaS that needs identity as programmable infrastructure. Its architecture is event-sourced — every change to identity state is stored as an immutable event, so the complete, replayable history of who changed what and when is a property of the design, not a logging feature you bolt on — and for a regulated business that has to *prove* its access controls, an audit trail that cannot be silently edited is a meaningful primitive. Tenancy is first-class: Organizations are isolated tenants, each with its own users, branding, and IdP config — the model B2B platforms actually need, and the one Keycloak's realms approximate more awkwardly.
Zitadel is API-first in the literal sense — every capability is exposed over gRPC, connectRPC, and REST, so identity is managed as code rather than clicked together in a console — and passkeys/FIDO2 are first-class. One nuance matters for a sovereignty decision and deserves to be stated plainly: with the v3 release in 2025, Zitadel changed its main repository license from Apache 2.0 to AGPL v3.0 and dropped CockroachDB for PostgreSQL only. The license move is a deliberate copyleft stance — modifications offered as a service must be shared back — and it was not universally popular with downstream adopters, exactly the kind of governance fact you weigh before standing on a project for a decade. Self-hosted, in Swiss jurisdiction, with an immutable event log, Zitadel is a strong answer for API-driven, multi-tenant identity where data residency is a hard requirement.
# Zitadel is API-first: provision identity over its management API, not a console.
# Create a project, then an OIDC application, with a service-account token.
curl -s -X POST \
https://idp.example.internal/management/v1/projects \
-H "Authorization: Bearer $ZITADEL_PAT" \
-H "Content-Type: application/json" \
-d '{ "name": "customer-portal" }'
curl -s -X POST \
"https://idp.example.internal/management/v1/projects/$PROJECT_ID/apps/oidc" \
-H "Authorization: Bearer $ZITADEL_PAT" \
-H "Content-Type: application/json" \
-d '{
"name": "portal-web",
"redirectUris": ["https://portal.example.internal/callback"],
"responseTypes": ["OIDC_RESPONSE_TYPE_CODE"],
"grantTypes": ["OIDC_GRANT_TYPE_AUTHORIZATION_CODE"],
"authMethodType": "OIDC_AUTH_METHOD_TYPE_NONE",
"appType": "OIDC_APP_TYPE_USER_AGENT"
}'The Trust-Model Decision Matrix
Choosing among the three is not a feature beauty contest, because on the core protocols they substantially overlap — all three do OIDC, OAuth 2.0, SAML 2.0, and passkeys. The useful question is which *constraint* dominates your context. Need the widest protocol and identity-brokering surface, deep SAML, and a vendor-supported build? Keycloak is the incumbent standard and the safe institutional choice. Is your real problem a heterogeneous estate of applications — many speaking no SSO — that all need to sit behind one gate? Authentik's forward-auth outposts and flow model are purpose-built for it. Building multi-tenant SaaS, managing identity as code through an API, needing a tamper-evident audit trail? Zitadel's organizations and event sourcing fit best.
Migrating Off Okta Without a Big Bang
An identity migration sounds terrifying because identity, if it breaks, breaks everything at once — so the discipline that makes it safe is to never flip the whole estate in one move. Stand up your chosen IdP and, at first, federate *to* Okta: your new provider becomes an OIDC/SAML broker that trusts the incumbent as an upstream source, so nothing changes for users yet. Then migrate applications one client at a time — re-point each app's OIDC discovery URL or SAML metadata from Okta to your provider, verify, and move on. Each app is an independent, reversible step, and the rollback is a one-line change back to the old issuer.
User accounts come across without a forced reset: federate during the bake period, provision into the new store with SCIM as apps cut over, and migrate passwords lazily by capturing them on each user's first login through the federated path. The migration is also the moment to fix what the hosted setup let you ignore — enforce PKCE everywhere, turn on passkeys, and wire SCIM deprovisioning so disabling an account actually removes access downstream, the control auditors ask about and hosted sprawl quietly erodes. Keep Okta authoritative until the owned plane has carried real traffic long enough to trust, exactly as a careful GitHub-to-self-hosted forge migration runs both systems in parallel before cutting the cord.
# An app's config is the whole migration surface: one issuer swaps for another.
# Before — trusting the hosted IdP:
oidc:
issuer: https://acme.okta.com
client_id: 0oa1a2b3c4DEF
scopes: [openid, profile, email]
redirect_uri: https://app.example.internal/callback
# After — trusting the IdP you operate. Same standard protocol, new home:
oidc:
issuer: https://idp.example.internal/realms/workforce
client_id: internal-dashboard
scopes: [openid, profile, email]
redirect_uri: https://app.example.internal/callback
pkce: S256 # enforce it now; the old setup let you skip it
# Rollback is the inverse edit. Because both speak OIDC, the app never knew
# it changed providers — which is the entire point of keeping the seam standard.The Exit Ramp From Your Own IdP
The failure mode of any self-hosting project is to escape one lock-in and quietly build another. The architecture here is designed against that: keep every seam a standard protocol, and treat anything proprietary as a convenience you must be able to live without.
- Apps consume standard OIDC and SAML. Their only dependency is a discovery document and a token format. Moving from Keycloak to Zitadel to Authentik is, from each app's perspective, an issuer URL change — the same move that brought it in.
- Users export over SCIM. The directory is not trapped in a proprietary schema; SCIM (RFC 7644) reads and writes accounts as standard JSON, so the user store walks to the next provider rather than being re-keyed by hand.
- Passkeys are WebAuthn credentials. Strong authentication rides the open FIDO2/WebAuthn standard, not a vendor SDK. The one honest caveat: a credential is bound to a relying-party identifier, so a domain change means re-enrolling factors — plan the identifier once, deliberately.
- The lock-in risk is the clever layer, not the core. Keycloak's fine-grained authorization, Authentik's elaborate flows, and Zitadel's actions are each useful and each the thing you cannot trivially export. Use them, but keep authorization decisions your apps can also derive from a standard token, so the IdP stays replaceable.
Held to that discipline, the token is the contract and the provider behind it is an implementation detail — optionality as a property you build rather than buy. Not the absence of an identity dependency, but one you can replace on your own schedule, through a migration that is per-app and reversible instead of a company-wide emergency.
The Long Game: An Identity Plane You Can Audit in 2035
Identity is decade-horizon infrastructure. The accounts, roles, and trust relationships you define this year will still govern access long after today's tooling debate is forgotten, which is why each project's *governance* matters as much as its feature list. Keycloak under the CNCF, Authentik under a public-benefit company with an open core, Zitadel under a copyleft license and Swiss jurisdiction — different structures, and the right one is the one whose incentives you trust to still be aligned in 2035. Open source is the method, not a checkbox: the guarantee is not just that you can read the code, but that the project cannot be acquired out from under you and quietly re-priced.
None of this is a mandate to rip Okta out on Monday. Sovereignty is pursued in proportion to capacity: a team that cannot reliably run, patch, and fail over its own IdP should not bet every login on one yet — a poorly run identity plane is genuinely worse than a well-run hosted one. The credential and session data an IdP holds is among the most sensitive in the company, which makes the secrets and key management discipline around it non-negotiable. Staying on Okta with a tested, rehearsed exit plan is itself a sovereign decision; drifting deeper into a per-MAU dependency because leaving feels hard is not.
When the support-system breach news broke, we spent a day working out which of our apps could have had sessions hijacked. The answer was all of them, because every login in the company went through one vendor we didn't operate and whose logs we couldn't see. We didn't migrate in a panic — we stood up our own provider as a broker, moved apps one OIDC client at a time over a quarter, and now identity is something we can put in front of an auditor. The point was never to dislike Okta. It was to be able to leave it.
That is the sovereign identity plane in one idea: not a rejection of the hosted world, but the standing ability to step out of it on your own terms. Keycloak, Authentik, and Zitadel are three good providers with genuinely different trust models, but the architecture is the real product — one owned IdP brokering every login, joined to every app only by OIDC, SAML, and SCIM, replaceable without disturbing anything around it. Build it that way and the next change in someone else's pricing or terms becomes a memo you read, not an emergency you absorb.
§FAQ/Common questions
Frequently asked
What is the best self-hosted alternative to Okta in 2026?
There is no single best product, because the right choice depends on which constraint dominates your environment. Keycloak is the mature, CNCF-backed incumbent with the broadest protocol and identity-brokering support and an available vendor-supported build (Red Hat build of Keycloak) — the safe institutional default, at the cost of higher operational weight. Authentik is the strongest fit when you have many heterogeneous applications, including ones that speak no SSO, that all need to sit behind one login — its proxy outposts do forward authentication for apps that were never built for it. Zitadel suits multi-tenant SaaS and API-first teams that want identity managed as code and an event-sourced, tamper-evident audit trail. All three speak OIDC, OAuth 2.0, SAML 2.0, and passkeys, so the decision is a trust-model and operating-model question, not a protocol one.
Keycloak vs Authentik vs Zitadel — how do I choose?
Decide by the dominant constraint rather than the feature grid. Choose Keycloak for the widest protocol surface, deep SAML and identity brokering, a huge community, and the option of Red Hat commercial support — accepting that it needs an external PostgreSQL database, cluster coordination, and a real configuration learning curve. Choose Authentik when your problem is application-centric: a sprawl of internal and legacy tools that need a single authentication gate, solved with its forward-auth proxy outposts and customizable flow/stage model (and, since 2025.10, a Redis-free, PostgreSQL-only deployment). Choose Zitadel for multi-tenant B2B SaaS that wants first-class organizations, an API-first (gRPC/REST) management model, passkeys-first authentication, and an immutable event-sourced audit history — noting that its v3 codebase is licensed AGPL v3.0.
Is a self-hosted identity provider secure enough for NIS2 or DORA audits?
It can be, and in some respects it is easier to evidence than a hosted IdP. NIS2 and DORA require documented access controls, rapid identity-based incident response, and audit trails you can produce on demand. Self-hosting puts the authentication logs, the user store, and the data-residency decision inside your own boundary, which makes those controls directly demonstrable — you are not asking a third party for log access you may not get. The trade is that you now own the operational security of the highest-value target in your estate: it must be patched, backed up, monitored, and failed over with discipline, and the secrets and signing keys it holds must be managed accordingly. A well-run self-hosted IdP is audit-grade; a neglected one is a liability, so the deciding factor is operational maturity, not the hosting model itself.
How do I migrate from Okta to a self-hosted IdP without downtime?
Migrate incrementally and keep Okta authoritative until the owned plane is proven. Stand up your chosen provider and federate it to Okta as an upstream identity source, so you have inserted a layer you control without changing anything for users. Then cut applications over one at a time by re-pointing each app's OIDC discovery URL or SAML metadata to your provider, verifying each before moving on — every app is an independent, reversible step. Provision users into the new store with SCIM as apps migrate, and migrate passwords lazily by capturing them on first login through the federated path so no mass reset is needed. Use the migration to enforce PKCE, enable passkeys, and wire SCIM deprovisioning. Keep Okta in place until the new IdP has carried real traffic long enough to trust; the rollback for any app is simply pointing its issuer URL back.
How is self-hosted SSO different from SPIFFE/SPIRE workload identity?
They solve two different planes of the identity problem. SPIFFE/SPIRE provides machine (workload) identity: services prove themselves to each other with attested, short-lived cryptographic credentials (SVIDs) and mutual TLS, with no human and no password involved. Keycloak, Authentik, and Zitadel provide human identity: people authenticate interactively through OIDC, OAuth 2.0, or SAML, hold sessions, use MFA or passkeys, grant consent, and must be deprovisioned when they leave. The protocols, lifetimes, and failure modes are different, and neither tool replaces the other. A mature architecture runs both — cryptographic identity for workloads and a sovereign SSO provider for people — with a clear seam where a human's token authorizes an action a workload then carries out.
Further reading
- Schrems II, six years on: EU data on US clouds is still unsafe
- Digital sovereignty: from policy slogan to testable architecture
- Gaia-X in practice: federation, labels, and the sovereignty gap
- SPIFFE and SPIRE: zero-trust workload identity and mTLS on Kubernetes
- Kubernetes secrets management with External Secrets Operator and Vault
- NIS2, DORA, and the EU AI Act for self-hosted Kubernetes
- Own your pipeline: Forgejo, Woodpecker, and Zot as a GitHub Actions exit
- Capabilities: how Stribog builds sovereign platforms
Executive Briefing
Thirty minutes to clarify your infrastructure risk
Walk us through your vendor footprint and regulatory constraints. We will tell you honestly where sovereignty creates leverage — and where it does not. No pitch deck. No obligation.