Migrating from Keycloak
The plan is the one of the Okta guide: OIDC to OIDC, an instance profile, users imported with their ids, then a dual-issuer period. This page lists what is specific to Keycloak.
Configure the instance
COMPAT_PROFILE=keycloak
Tokens are signed with RS256, a request without resource yields a JWT for the application url, and tokens carry groups (with the roles scope) and preferred_username (with profile). Keycloak's own endpoint paths are not aliased: configure clients with the discovery URL https://auth.example.com/.well-known/openid-configuration. See Compatibility profiles.
What changes
| In Keycloak | In auth-service |
|---|---|
| Realm | The whole instance. Users are global and each application grants them access. Several realms with overlapping users need merging (the e-mail is the unique key) or separate instances |
Issuer https://kc/realms/<realm> | https://auth.example.com |
Endpoints under /protocol/openid-connect/ | The /api/auth/… endpoints; keys at /api/auth/jwks |
Realm and client roles in realm_access.roles and resource_access.<client>.roles | The application's roles, in the flat roles claim (and groups with the profile) |
| Client scopes and mappers | Roles, permissions, plans, claim mappings; the scope list is fixed |
aud set by an audience mapper | aud is the requested resource, or the application url by default |
| Client secret: Basic or POST | HTTP Basic, unless TOKEN_ENDPOINT_AUTH_METHOD=post |
| User federation (LDAP, Kerberos), SAML clients, UMA / authorization services, token exchange | Not available. Social login is limited to Google and GitHub |
Move the users
Export the realm with its users, for example kc.sh export --realm <realm> --users realm_file --file realm-export.json (the admin console's partial export does not include users), then:
pnpm migrate:keycloak -- --file realm-export.json --app acme-app # dry run
AUTH_SERVICE_URL=https://auth.example.com ADMIN_EMAIL=… ADMIN_PASSWORD=… \
pnpm migrate:keycloak -- --file realm-export.json --app acme-app --apply
The command reads the users with their ids, verified flag and PBKDF2 credentials (pbkdf2-sha256 and pbkdf2-sha512), which are imported so that users keep their password (it is re-hashed natively at their first sign-in). Groups become roles (the last segment of the group path, normalised into a role name); users without groups use their realm roles, without the built-in ones (default-roles-*, offline_access, uma_authorization). Service accounts and users without an e-mail address are skipped. A credential in another algorithm (for example argon2) is reported as unsupported-hash: those users reset their password. The options, the environment variables, the superadmin requirement and the report are those of the Okta command, except that --file is required and --trust-email defaults to true here: it marks as verified the addresses that the export says are verified (emailVerified).
The export contains password hashes: delete it when the migration is done.