Keycloak
Sign in with Keycloak and let an AuthCrunch policy protect your application. This walkthrough admits members of one Keycloak group. Other users can sign in to the portal but cannot access the app.
The integration was tested with Keycloak 26.7.4 and the published caddy-security v1.3.0 bundle, containing go-authcrunch v1.3.8. Real browser logins against a disposable local Keycloak instance verified PKCE, claim mapping, and application access. Public HTTPS and your deployment's network paths still need verification.
Before you start
Have an existing Keycloak server and permission to manage a realm and client. For a new server, follow Keycloak's getting-started guide and production configuration guidance. Complete Install and verify for the AuthCrunch binary.
This example uses two public HTTPS hosts. Replace them with your own:
| Name | Value in this guide |
|---|---|
| Keycloak server | https://id.example.com |
| Keycloak realm | example |
| Keycloak client ID | authcrunch |
| AuthCrunch portal | https://auth.example.com/auth/ |
| AuthCrunch provider name and realm | keycloak |
| Protected application | https://auth.example.com/app |
The Keycloak realm selects the tenant. The AuthCrunch realm identifies the login source inside the portal. They serve different purposes and need not share a name.
Realm
Create a realm named example, or select an existing application realm. Keep
master for Keycloak administration, as described in
Keycloak's realm setup.
Leave the realm's signing-key providers in place; this integration retrieves
public verification keys through discovery.
Check this URL from the AuthCrunch server:
https://id.example.com/realms/example/.well-known/openid-configuration
The response's issuer should be https://id.example.com/realms/example, and
its endpoints must be reachable by the browser or server that uses them. Current
Keycloak deployments use /realms/… by default. Include an additional /auth
prefix only if your deployment actually configures it. See
Keycloak's OIDC endpoints.
Earlier console reference: realm signing keys
These images document the old realm-key screens. Keep the current realm signing-key providers enabled as described above; the disabled switches and reduced key list in these historical captures are not steps to reproduce. The current integration discovers the provider’s public keys.



Client
In the example realm, create an OpenID Connect client with client ID
authcrunch. Configure these capabilities and URLs:
| Setting | Value |
|---|---|
| Client authentication | On; AuthCrunch is a confidential server-side client |
| Standard flow | On |
| Implicit flow, Direct access grants, Service accounts | Off for this integration |
| Home URL | https://auth.example.com/auth/ |
| Valid redirect URIs | https://auth.example.com/auth/oauth2/keycloak/authorization-code-callback |
| Require PKCE | On |
| PKCE Method | S256 |
Use the exact redirect URI. The code exchange happens on the server; the walkthrough does not require a wildcard redirect or browser CORS access to the token endpoint. Save the client and copy its secret from Credentials. Keep the client secret out of the browser and repository.
The generic driver sends the client ID and secret in the token request body. Use Keycloak's client ID/secret authentication for this setup, rather than a signed client assertion. The driver already sends S256 PKCE; configure the client to require that method. See the Keycloak OIDC client settings.
Screenshots: client registration in the earlier Keycloak console
The older console used Access Type = confidential; current Keycloak uses Client authentication = On. The screenshots use the master realm and old example URLs. Use the application realm, exact callback, flow settings, and S256 PKCE from this guide.





Earlier console reference: client keys and credentials
This sequence came from the old client-key walkthrough. The current configuration authenticates with a client secret from Credentials; it does not require generating a JKS archive or reusing a key-store password as that secret. The password fields and exported secret below are redacted.



Map groups into the ID token
Keep the client's profile and email scopes. Add a Group Membership
protocol mapper for this client, with these settings:
| Mapper setting | Value |
|---|---|
| Name | authcrunch-groups |
| Token Claim Name | groups |
| Full group path | On |
| Add to ID token | On |
| Add to access token | Off for this example |
| Add to userinfo | Off for this example |

Use the client's dedicated scope so this mapping applies to this client. In the client, open Client scopes, select its dedicated scope, and add a mapper By configuration → Group Membership. This is ordinary realm group membership, not an Organization Group Membership mapper.
With full paths enabled, a member of the top-level app-members group receives
this ID-token claim:
{
"groups": ["/app-members"]
}
The leading slash matters. AuthCrunch combines group claims into its roles,
so the Caddyfile matches the exact role /app-members. Putting the mapper only
on UserInfo does not supply this example's ID-token claim.
Screenshots: claim mappers in the earlier console
Current Keycloak keeps this configuration in the client’s dedicated scope. These older screenshots show the previous Mappers tab and help connect the email/groups claim names to their protocol mappers.


Groups
Create a top-level group named app-members. Assign only the intended
application users to it. In this walkthrough, Alice is a member and Bob is not.
| Stage | Access information |
|---|---|
| Keycloak membership | Alice belongs to /app-members |
| Keycloak ID token | Contains groups: ["/app-members"] |
| AuthCrunch transform | Matches realm keycloak and role /app-members |
| AuthCrunch application token | Receives app/member |
| Application policy | Allows app/member |
The transform also gives every signed-in Keycloak user authp/user for ordinary
portal access. That role does not meet the application's rule.
Screenshots: group creation and role mappings in the earlier console
The older example used Admins, Editors, and Viewers groups with portal roles. The current example needs one app-members group and a separate AuthCrunch transform that grants app/member. These screenshots illustrate the group editor and where role mappings were displayed; do not grant portal administration just to permit app access.









Users
Create or choose two users in the same realm:
| User | Group membership | Expected /app result |
|---|---|---|
| Alice | /app-members | 200, protected response |
| Bob | No /app-members membership | 403, no protected response |
Set each user's email and credentials. The generic driver requires an email
claim in the ID token by default, so retain the email client scope and its
ID-token mapping. For disposable test users, set a password in Credentials;
if it is temporary, complete Keycloak's required password change during login.
Assign Alice through the user's Groups tab. An email address or a similarly named Keycloak role does not automatically place a user in this group.
Screenshots: users, credentials, and membership
These earlier screens remain useful for locating user creation and credentials. Use ordinary test users in your application realm, set their email, and give only Alice membership in app-members.



Realm Roles
Existing deployments may prefer realm roles over a groups mapper. The bundled
generic driver recognizes realm_access.roles as well as top-level roles.
Configure the relevant realm-role mapper to include the intended role in the
ID token, then replace the group matcher with that role name. For example:
transform user {
match realm keycloak
match role example-app-member
action add role app/member
}
This is an alternative to the group-granting transform. Keeping both grants
would allow either one to add app/member. Nested client roles under
resource_access.CLIENT.roles are not extracted by this release's generic
parser; map the needed values into a supported claim. See the
generic claim reference.
There is no need to give application members authp/admin in Keycloak.
The example keeps portal permissions and application permissions separate.
Screenshots: realm-role alternative in the earlier console
These images show the earlier Add Role and role detail screens. For the alternative above, use a dedicated application role such as example-app-member. The old authp/admin, authp/user, and authp/guest names are historical examples and are not required Keycloak roles for this walkthrough.






Configure AuthCrunch
Save this as Caddyfile. Replace auth.example.com in both the site address and
policy login URL. Replace id.example.com and example in the issuer and
metadata URLs to match your Keycloak deployment. Keep the registered callback
in sync with the public portal hostname and /auth/ mount.
The example is embedded from the canonical Keycloak Caddyfile.
# Set the public portal hostname and the provider issuer/discovery URLs.
{
admin off
persist_config off
security {
oauth identity provider keycloak {
driver generic
realm keycloak
client_id {env.KEYCLOAK_CLIENT_ID}
client_secret {env.KEYCLOAK_CLIENT_SECRET}
scopes openid email profile
issuer https://id.example.com/realms/example
metadata_url https://id.example.com/realms/example/.well-known/openid-configuration
login icon text "Keycloak"
}
authentication portal myportal {
enable identity provider keycloak
crypto default token lifetime 900
crypto key sign-verify {env.JWT_SHARED_KEY}
cookie path /
ui {
links {
"Example app" /app icon "las la-external-link-alt"
"My identity" /auth/whoami icon "las la-user"
}
}
transform user {
match realm keycloak
action add role authp/user
}
transform user {
match realm keycloak
match role /app-members
action add role app/member
}
}
authorization policy apppolicy {
set auth url https://auth.example.com/auth/
crypto key verify {env.JWT_SHARED_KEY}
allow roles app/member
}
}
}
auth.example.com {
route {
redir /auth /auth/ 308
authenticate /auth/* with myportal
@app path /app /app/*
route @app {
authorize with apppolicy
respond "You reached the Keycloak-protected app." 200
}
respond "AuthCrunch is running. Open /app to begin." 200
}
}
Supply these values to the AuthCrunch process:
export KEYCLOAK_CLIENT_ID='authcrunch'
export KEYCLOAK_CLIENT_SECRET='YOUR_KEYCLOAK_CLIENT_SECRET'
export JWT_SHARED_KEY="$(openssl rand -hex 32)"
Replace the secret placeholder with the client secret from Keycloak. For a managed service, store these values in its protected environment and reuse the signing key across restarts. The portal and policy must use the same key.
Start Keycloak before AuthCrunch, then check and run the Caddyfile:
./bin/authcrunch adapt --adapter caddyfile --config Caddyfile >/dev/null
./bin/authcrunch run --config Caddyfile
Adaptation checks parsing. Startup loads the discovery document and keys, and Caddy provisions HTTPS for the public portal host. DNS and ports 80/443 must be configured for your certificate setup. The example disables the admin endpoint; use Ctrl+C and restart the foreground process after edits.
The protected handler currently responds with a message. Replace that respond
with your application's reverse_proxy after verification. Keep authorize
before it and preserve the matcher covering /app and /app/*.
User Login
- Open
https://auth.example.com/app. The policy sends you to the portal. - Choose Keycloak and sign in as Alice.
- If you reach the portal's Applications page, select Example app.
You should see
You reached the Keycloak-protected app. - Select My identity or visit
/auth/whoami. Confirm thatrolesincludes/app-members,authp/user, andapp/member, andrealmiskeycloak. - In a separate browser session, sign in as Bob. He should reach the portal,
but
/app,/app/, and/app/nestedshould return 403.
Membership and transforms are evaluated at login. Removing Alice from the group does not rewrite an existing AuthCrunch token. This example uses a 900-second token lifetime; test changed membership with a fresh login.
Screenshots: sign-in, account applications, and portal identity
These captures show the older Keycloak account console and AuthCrunch identity view. They illustrate where a user signs in and inspects roles. The current expected roles are /app-members, authp/user, and app/member; the old account names, hostnames, and role set are illustrative only.



Check logout separately
The portal's Sign out action clears its browser session. In this configuration, it does not end the Keycloak SSO session. A subsequent Keycloak login can issue a new portal token without asking for a password again. Use separate browser sessions when testing Alice and Bob; portal logout alone does not switch their Keycloak account.
Troubleshoot
| Symptom | Check |
|---|---|
| Discovery fails at startup | Verify the realm's full discovery URL, configured context path, DNS, and TLS trust from the AuthCrunch server. |
invalid_redirect_uri | Match the registered callback to the portal hostname, /auth/ mount, and AuthCrunch realm keycloak. |
| Client authentication fails | Verify Client authentication is On and the process has the current client ID and secret. |
| Issuer mismatch | Compare the configured issuer with Keycloak's discovery document and ID token; proxy hostname settings and realm paths must agree. |
| PKCE failure | Require S256 at the client and leave PKCE enabled in AuthCrunch. Start a fresh login flow. |
| Email claim missing | Give the user an email and include it in the ID token through the client's email scope. |
| Alice signs in but gets 403 | Check membership, mapper placement, Add to ID token, and the leading slash in /app-members; inspect the resulting roles after a new login. |
| Bob reaches the app | Check for another transform or provider role that grants app/member, and ensure the policy requires that role rather than authp/user. |
| Login resumes immediately after logout | The Keycloak SSO session is still active; use a separate browser session for a different account. |
For discovery overrides, supported claim paths, and optional UserInfo behavior, continue to Generic OpenID Connect.