How to Migrate from Auth0 to Keycloak 2026
How to Migrate from Auth0 to Keycloak 2026
Auth0's pricing climbs fast once you need SSO, MFA, or more than the free tier's 7,500 MAU. At 10,000 MAU with SSO enabled, you're looking at $228/month or more. Keycloak is the enterprise-grade open source alternative — OIDC, SAML, LDAP, Kerberos, MFA, and fine-grained authorization — self-hosted at the cost of your infrastructure. Here's how to migrate.
TL;DR
Deploy Keycloak with PostgreSQL, create a realm and client, export Auth0 users and import them via the Admin API (users need password resets), configure social providers as Identity Providers, and update your application to use Keycloak's OIDC endpoint. Most application code changes are just swapping Auth0's domain for your Keycloak URL.
Key Takeaways
- Keycloak is fully OIDC/OAuth2 compliant — any standard OIDC library works (next-auth, passport, etc.)
- Auth0 passwords cannot be migrated — users must reset or authenticate via social login
- Keycloak's realm concept maps directly to Auth0's tenants
- Social providers (Google, GitHub, Apple) are configured as Identity Providers in Keycloak
- Production Keycloak requires PostgreSQL — not the embedded H2 database
- SSO across multiple apps is free and unlimited in Keycloak; on Auth0 it's an enterprise-tier add-on
Why Teams Leave Auth0
Auth0's free tier is genuinely useful for development. But the pricing escalates sharply. The Essential plan caps at 7,500 MAU and adds per-user fees beyond that threshold. Enterprise features like SSO, SAML, and advanced MFA are locked behind significantly higher tiers. For B2B SaaS products where SSO is a customer requirement, Auth0 becomes expensive quickly — often $500-1,500/month for mid-sized businesses.
Keycloak is Red Hat's open source identity platform, deployed in production by enterprises globally. It supports OIDC, SAML 2.0, LDAP/Active Directory federation, Kerberos, and more. Running it yourself on a $20-40/month VPS serves unlimited MAU with all enterprise features included at no additional licensing cost.
Before choosing Keycloak, compare it against Authentik and Logto. Authentik has a more modern UI and lower resource usage. Logto is better for developer-facing API auth. For enterprises needing full SAML, Active Directory, and Kerberos support, Keycloak is the right choice. The full comparison of self-hosted SSO options covers all three.
Step 1: Deploy Keycloak
For production, use Docker Compose with PostgreSQL. The default H2 in-memory database is for development only and loses all data on restart.
# docker-compose.yml
services:
db:
image: postgres:15
environment:
POSTGRES_DB: keycloak
POSTGRES_USER: keycloak
POSTGRES_PASSWORD: secret
volumes:
- pg-data:/var/lib/postgresql/data
keycloak:
image: quay.io/keycloak/keycloak:latest
command: start
environment:
KC_BOOTSTRAP_ADMIN_USERNAME: admin
KC_BOOTSTRAP_ADMIN_PASSWORD: changeme
KC_DB: postgres
KC_DB_URL: jdbc:postgresql://db:5432/keycloak
KC_DB_USERNAME: keycloak
KC_DB_PASSWORD: secret
KC_HOSTNAME: auth.yourdomain.com
KC_PROXY: edge
KC_HTTP_ENABLED: "true"
ports:
- "8080:8080"
depends_on:
- db
volumes:
pg-data:
docker compose up -d
Put Keycloak behind an nginx reverse proxy with Let's Encrypt SSL. Set KC_HOSTNAME to your actual domain. The KC_PROXY: edge setting tells Keycloak it's behind a TLS-terminating proxy.
Keycloak requires at least 2 GB RAM in production. Budget 400 MB–1 GB under normal load, with 2 GB for safety during startup and garbage collection cycles. A 2 vCPU / 4 GB RAM VPS ($12-20/month on Hetzner or DigitalOcean) handles most production workloads.
Step 2: Create Realm and Client
In Keycloak, a realm is equivalent to an Auth0 tenant — a separate namespace for users, clients, and identity settings. Create one realm per environment (production, staging) or per customer for multi-tenant B2B applications.
- Log in to
https://auth.yourdomain.com/adminwith your bootstrap credentials - Click the realm dropdown → Create realm → name it "production"
- Under Clients, click Create client:
- Client type:
OpenID Connect - Client ID:
my-app - Client authentication: On (for confidential server-side clients)
- Valid redirect URIs:
https://myapp.com/callback - Valid post-logout redirect URIs:
https://myapp.com
- Client type:
- In the Credentials tab, copy the client secret
Realm settings configure token lifetimes, session durations, brute-force detection, and password policy. Set these appropriately before importing users — imported users will be subject to your password policy on first login.
Step 3: Export Auth0 Users
# Use Auth0 Management API
curl -X GET "https://YOUR_DOMAIN.auth0.com/api/v2/users?per_page=100&page=0" \
-H "Authorization: Bearer YOUR_MGMT_TOKEN" \
> auth0-users.json
For large user bases, use Auth0's Export Job:
curl -X POST "https://YOUR_DOMAIN.auth0.com/api/v2/jobs/users-exports" \
-H "Authorization: Bearer YOUR_MGMT_TOKEN" \
-H "Content-Type: application/json" \
-d '{"format": "json", "fields": [{"name": "email"}, {"name": "name"}, {"name": "email_verified"}, {"name": "user_id"}, {"name": "given_name"}, {"name": "family_name"}]}'
The export job runs asynchronously. Poll the job endpoint until status is "completed", then download the result file. For very large user bases, export in batches by filtering by creation date range.
Step 4: Import Users to Keycloak
# Python script to import users via Keycloak Admin API
import requests
KEYCLOAK_URL = "https://auth.yourdomain.com"
REALM = "production"
# Get admin token
token = requests.post(f"{KEYCLOAK_URL}/realms/master/protocol/openid-connect/token", data={
"grant_type": "client_credentials",
"client_id": "admin-cli",
"client_secret": "admin-secret",
}).json()["access_token"]
# Import users
for user in auth0_users:
requests.post(
f"{KEYCLOAK_URL}/admin/realms/{REALM}/users",
headers={"Authorization": f"Bearer {token}"},
json={
"username": user["email"],
"email": user["email"],
"emailVerified": user["email_verified"],
"enabled": True,
"firstName": user.get("given_name", ""),
"lastName": user.get("family_name", ""),
"requiredActions": ["UPDATE_PASSWORD"],
"attributes": {
"auth0_user_id": [user["user_id"]],
},
}
)
The requiredActions: ["UPDATE_PASSWORD"] flag forces users to set a new password on first login. This is unavoidable because Auth0's scrypt password hashes are incompatible with Keycloak's bcrypt implementation. Storing the Auth0 user ID as an attribute allows cross-referencing during a parallel transition period.
For large imports, batch users in groups of 100-500 and add error handling for duplicates. Import can take 10-30 minutes for tens of thousands of users due to rate limiting on the Admin API.
Step 5: Configure Social Login
| Auth0 Connection | Keycloak Setup |
|---|---|
| Identity Providers → Google | |
| GitHub | Identity Providers → GitHub |
| Apple | Identity Providers → Apple |
| Identity Providers → Facebook | |
| SAML enterprise | Identity Providers → SAML 2.0 |
| Microsoft / Azure AD | Identity Providers → Microsoft |
For each social provider: create an OAuth app on the provider's developer console, then add the Client ID and Client Secret in Keycloak's Identity Provider configuration. Keycloak generates a redirect URI you'll need to register with each provider.
Users who signed in via social login on Auth0 don't need password resets. When they click "Continue with Google" on your updated application, Keycloak authenticates via Google and links the account by email. Social login accounts are the smoothest part of the migration — zero friction for those users.
After configuring social providers, test the full login flow in your staging environment before migrating production users. Verify that tokens contain the expected claims (email, name, roles) and that your backend correctly validates Keycloak-issued JWTs.
Step 6: Update Application Code
Before (Auth0 SPA SDK):
import { Auth0Client } from '@auth0/auth0-spa-js';
const auth0 = new Auth0Client({
domain: 'your-tenant.auth0.com',
clientId: 'YOUR_CLIENT_ID',
});
await auth0.loginWithRedirect();
After (Keycloak — using OIDC):
import Keycloak from 'keycloak-js';
const keycloak = new Keycloak({
url: 'https://auth.yourdomain.com',
realm: 'production',
clientId: 'my-app',
});
await keycloak.init({ onLoad: 'login-required' });
Or use any OIDC library (Keycloak is standard OIDC):
// next-auth example
import KeycloakProvider from "next-auth/providers/keycloak";
export const authOptions = {
providers: [
KeycloakProvider({
clientId: "my-app",
clientSecret: "secret",
issuer: "https://auth.yourdomain.com/realms/production",
}),
],
};
Because Keycloak is fully OIDC-compliant, you're not locked into any particular client library. Any library that accepts an OIDC issuer URL works: next-auth, passport-openidconnect, oidc-client-ts, and others. This is a significant improvement over Auth0, where using the official SDK creates dependency on Auth0-specific APIs.
Backend token validation (Python FastAPI example):
from jose import jwt
import requests
KEYCLOAK_URL = "https://auth.yourdomain.com"
REALM = "production"
def get_public_key():
certs_url = f"{KEYCLOAK_URL}/realms/{REALM}/protocol/openid-connect/certs"
return requests.get(certs_url).json()
def verify_token(token: str):
jwks = get_public_key()
header = jwt.get_unverified_header(token)
key = next(k for k in jwks["keys"] if k["kid"] == header["kid"])
return jwt.decode(token, key, algorithms=["RS256"], audience="my-app")
Step 7: Set Up MFA
- Authentication → Flows → Browser
- Add OTP Form step after username/password
- Or add WebAuthn for passkey support
- Configure as Required or Conditional (only for specific user groups)
Keycloak supports TOTP (Google Authenticator, Authy), WebAuthn/FIDO2 (passkeys, hardware keys), and email OTP out of the box. MFA configuration is per-realm and can be made mandatory for specific roles or groups — requiring MFA only for admin users, for example, while keeping a smooth login flow for regular users.
Auth0's "Adaptive MFA" that triggers based on risk signals is a premium feature. Keycloak's conditional MFA based on group membership covers most enterprise requirements at no additional cost.
Configuring Roles and Groups
Keycloak's role model is more granular than Auth0's. Auth0 has roles and permissions. Keycloak has realm roles, client roles, composite roles, and groups — all mappable to JWT token claims.
Realm roles are global (like "admin", "user"). Client roles are app-specific (like "billing-admin" in "my-app"). Groups are organizational units that can have roles assigned. Composite roles bundle multiple roles into one assignment.
Configure claim mapping in Client Scopes → roles → Mappers. The default configuration includes realm and client roles in the token automatically.
Cost Comparison
| MAU | Auth0 | Keycloak Self-Hosted | Savings |
|---|---|---|---|
| 7,500 | Free | $20/month (VPS) | N/A |
| 10,000 | $228/month | $30/month | $2,376/year |
| 50,000 | $800+/month | $50/month | $9,000/year |
| Enterprise (SSO) | $1,500+/month | $80/month | $17,040/year |
Migration Timeline
| Week | Task |
|---|---|
| Week 1 | Deploy Keycloak, configure realm and client, import users |
| Week 2 | Set up social login, MFA, update application code |
| Week 3 | Testing, QA, parallel login support |
| Week 4 | Cutover, sunset Auth0 |
Common Pitfalls
H2 database in production: Keycloak ships with an H2 in-memory database for development. Never use it in production — it doesn't persist data across restarts. Always configure PostgreSQL before importing users.
Realm discovery URL: Auth0 uses https://your-tenant.auth0.com/.well-known/openid-configuration. Keycloak uses https://auth.yourdomain.com/realms/production/.well-known/openid-configuration. Update all backend OIDC configurations to use the realm-scoped discovery URL, not just the base Keycloak URL.
Token claim differences: Auth0 includes claims like sub, email, and nickname in specific standard positions. Keycloak's default token structure differs slightly — especially for roles, which live in a nested realm_access.roles array. Check your backend for hardcoded claim path assumptions and update accordingly or configure Keycloak's mappers to emit Auth0-compatible claims.
Running Auth0 and Keycloak in Parallel
For production applications, the recommended migration approach runs both identity providers simultaneously for two to four weeks. Configure Keycloak as a secondary provider, migrate a subset of users, validate the full auth flow end-to-end including token validation, refresh token handling, social login, and logout. Only after you've confirmed the complete auth lifecycle works in production does the main user population migrate.
The parallel approach requires temporarily maintaining two sets of application credentials — Auth0 client IDs and Keycloak client IDs — in your environment configuration. Use a feature flag or percentage rollout to route some percentage of new logins to Keycloak while existing sessions remain on Auth0. As users re-authenticate, they move to Keycloak naturally. After the cutover window, any remaining Auth0 sessions expire and those users complete their transition on next login.
Password reset is the key UX challenge. Since Auth0 passwords can't be exported, users authenticating with email/password need to reset their password to continue using the application via Keycloak. Trigger reset emails proactively for high-activity users in the week before cutover. Include clear communication in the password reset email explaining the transition — users are less likely to follow a reset link if they don't understand why it's necessary.
Backup and Disaster Recovery for Keycloak
Keycloak's state is stored entirely in the PostgreSQL database you configure. This means your disaster recovery plan is the same as your database disaster recovery plan: regular backups with tested restore procedures. The Keycloak application itself is stateless once connected to a database — if the container fails, restart it and it reconnects to the database in full working state.
Back up the PostgreSQL database daily at minimum. Tools like pg_dump output a portable SQL file that restores cleanly into any PostgreSQL version. Store backups off-server — S3, Backblaze B2, or a separate VPS — and test the restore process quarterly. A backup you've never tested is no backup at all. For the full Keycloak deployment including realm configuration, also export the realm JSON periodically via Realm Settings → Action → Partial Export. This gives you a human-readable record of your realm configuration separate from the database backup.
The Bottom Line
Keycloak is one of the most complete open source identity platforms available. The migration from Auth0 requires meaningful configuration work — realm setup, social providers, and token claim mapping — but once deployed, Keycloak provides every enterprise identity feature at the cost of your infrastructure alone.
Compare authentication platforms on OSSAlt — protocol support, features, and pricing side by side.
See open source alternatives to Auth0 on OSSAlt.