Keycloak sits as a proxy between your identity provider applications (Active Directory, RADIUS, SAML) and your identity consumer applications. If you’re reading this article, you’ve probably already discovered how Keycloak can drastically reduce complexity both for users and administrators. What you may not know is that Keycloak can log Active Directory users in automatically, as soon as they open the auth page. Incredible! No username or password at all? No more users asking which password to use?
That’s what I thought too…
After struggling for 3 days through bad documentation to get this working, with countless tabs open and no Claude credit left, I finally got this absolute unit of an auth solution working. I wrote this guide to help you avoid the same fate.
All commands prefixed with PS> run on the Domain Controller. Commands prefixed with $ run on the Keycloak host. Client-side commands are labelled explicitly.
Prerequisites
Hardware
This guide is intended to be used for AD-joined Windows computers accessing Keycloak through either Chrome or Edge. No other configurations have been tested.
Keycloak
- Recent version of Keycloak (we tested with 26)
- LDAP Federation in Keycloak — you should be able to log in with username and password against AD before starting this guide
- HTTPS. Your Keycloak instance needs to be accessible via https, an http endpoint will not work with Kerberos
- A public DNS name for your sign-in endpoint, we will need to set this in browser policy later
- Access to the Keycloak filesystem to edit config files
Active Directory
- Recent version of AD, we used Windows Server 2022
- Admin access to the Users and Computers console, you will need to create a service account and issue a Kerberos keytab
- Access to Group Policy editor
- Access to SYSVOL, we will be adding custom ADMX definitions for Chrome & Edge
Active Directory Setup
Creating a Service Account
Open Active Directory Users and Computers and create a dedicated user account (we will use svc-keycloak for the example). Tick Password never expires and save the password somewhere secure, we will use it again later.
Make it a dedicated account — don’t reuse your LDAP bind account. Generating the keytab later will rotate the account password, and you don’t want that taking your LDAP binding down with it.
Enabling Kerberos Auth
Open the account properties for your service account and go to the Account tab. Under Account Options tick both:
- This account supports Kerberos AES 128 bit encryption
- This account supports Kerberos AES 256 bit encryption
Without this, the KDC issues RC4-encrypted tickets — and modern Keycloak runs on JDK 17+, which has removed RC4 from the default enabled cipher list. The tickets will simply be rejected.
Setting the SPN
We need to register our service account as the keyholder for our Keycloak web service:
PS> setspn -S HTTP/auth.example.com svc-keycloak The HTTP/ prefix is correct even for HTTPS — the SPN class is HTTP regardless of TLS. Microsoft’s SPN reference has the full spec.
PS> setspn -Q HTTP/auth.example.com This should return exactly one account. If you see multiple, you will need to remove the SPN from the old account before continuing — the KDC refuses to issue tickets when an SPN is ambiguous.
Generate the Keytab
A keytab is a file containing the long-term key derived from the service account password — Keycloak uses it to decrypt incoming SPNEGO tokens. Generate one on the Domain Controller:
PS> ktpass -princ HTTP/auth.example.com@YOURDOMAIN.COM -mapuser svc-keycloak@yourdomain.com -pass <password> -crypto AES256-SHA1 -ptype KRB5_NT_PRINCIPAL -out keycloak.keytab Note the realm in the principal is uppercase — this matters, and it will keep mattering throughout this guide. Verify the keytab was generated correctly:
PS> ktpass -in keycloak.keytab
# Should show: etype 0x12 (AES256-SHA1), correct principal, uppercase realm ktpass without -setpass rotates the service account password to a random value. If you reuse this account for anything else, including the LDAP bind, you will break those integrations at the moment of keytab generation. This is why we made a dedicated account.Keycloak Setup
Copy krb5.conf & Keytab
Copy the keytab from the Domain Controller to your Keycloak host (e.g. /etc/keycloak.keytab). This file is the long-term key for your service account, so treat it like a password: make it readable only by the Keycloak process — 0440 owned by the Keycloak service user is ideal.
$ chown keycloak:keycloak /etc/keycloak.keytab
$ chmod 0440 /etc/keycloak.keytab Next, create or verify /etc/krb5.conf on the Keycloak host. This is how the JVM resolves realms and decides which encryption types it will accept — without it, nothing downstream works. See the MIT Kerberos krb5.conf reference for a full breakdown of the sections and options.
[libdefaults]
default_realm = YOURDOMAIN.COM
dns_lookup_realm = false
dns_lookup_kdc = false
rdns = false
default_tgs_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
default_tkt_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
permitted_enctypes = aes256-cts-hmac-sha1-96 aes128-cts-hmac-sha1-96
[realms]
YOURDOMAIN.COM = {
kdc = <dc-ip>
admin_server = <dc-ip>
}
[domain_realm]
.yourdomain.com = YOURDOMAIN.COM
yourdomain.com = YOURDOMAIN.COM Restart Keycloak after both files are in place so the JVM picks them up.
Setup Kerberos Federation
In the Keycloak admin console, open your existing LDAP federation provider and find Kerberos Integration. Set:
- Allow Kerberos authentication —
ON - Kerberos Realm —
YOURDOMAIN.COM(must be uppercase) - Server Principal —
HTTP/auth.example.com@YOURDOMAIN.COM - KeyTab —
/etc/keycloak.keytab - Kerberos Principal Attribute — leave empty
That last setting deserves an explanation, because it cost me half a day. When SPNEGO succeeds, Keycloak extracts a principal like DamianV@YOURDOMAIN.COM from the ticket and needs to find a matching user in LDAP. If you set the principal attribute to userPrincipalName, Keycloak searches LDAP for the literal string DamianV@YOURDOMAIN.COM — but the UPN in AD might be damianv@yourdomain.co.nz, different case and a different domain suffix. The lookup returns zero rows and login fails.
Leaving the attribute empty tells Keycloak to strip the @REALM, take the username part (DamianV), and look it up via sAMAccountName. LDAP is case-insensitive, so DamianV matches damianv in your directory. This is almost always what you actually want.
sAMAccountName matching do its job.Adjust Browser Auth Flow
In Authentication → Flows → browser, make sure the Kerberos execution is enabled and positioned after Cookie but before Forms:
- Cookie — Alternative
- Kerberos — Alternative
- Forms — Alternative
If Kerberos is missing, disabled, or positioned after Forms, Keycloak will never send WWW-Authenticate: Negotiate and the browser will go straight to the login form. The Keycloak Server Admin Guide — Kerberos section has the canonical reference for these settings.
Group Policy Setup
Here’s the part the Keycloak docs won’t tell you: even with the server perfectly configured, no browser will attempt Kerberos against your endpoint by default. Because auth.example.com is an FQDN with dots, Windows does not classify it as intranet, so Chrome and Edge silently skip the Negotiate handshake. We fix that with browser policy, deployed domain-wide through Group Policy.
Install ADMX Definitions
Windows doesn’t ship policy templates for Chrome, and the built-in Edge ones are usually outdated, so we install them into the SYSVOL central store — this makes the policies available in Group Policy editor on every DC and admin workstation.
- Download the Chrome Enterprise bundle and the Microsoft Edge policy files and extract both
- Copy the
.admxfiles (chrome.admx,google.admx,msedge.admx) into:
\yourdomain.comSYSVOLyourdomain.comPoliciesPolicyDefinitions - Copy the matching
.admllanguage files into the language subfolder, e.g.:
\yourdomain.comSYSVOLyourdomain.comPoliciesPolicyDefinitionsen-US PolicyDefinitions doesn't exist in SYSVOL yet, your domain has never had a central store. Create the folder and seed it by copying C:\Windows\PolicyDefinitions from a current Windows machine, then add the Chrome and Edge templates on top.Close and reopen Group Policy editor, and you should see Google → Google Chrome and Microsoft Edge under Administrative Templates.
Create Group Policy
Open Group Policy Management, create a new GPO (we called ours Keycloak SSO — Browser Auth), and link it to the OU containing your workstations. Edit it and navigate to:
Computer Configuration → Policies → Administrative Templates → Google → Google Chrome → HTTP authentication
Set these two policies:
- Authentication server allowlist (
AuthServerAllowlist) →auth.example.com— tells Chrome it is allowed to answer a Negotiate challenge from this host - Disable CNAME lookup when negotiating Kerberos authentication (
DisableAuthNegotiateCnameLookup) →Enabled
Then repeat both under Microsoft Edge → HTTP authentication — the policy names are identical. See Chrome Enterprise HTTP authentication policies for the full list.
auth.example.com CNAMEs to server1.internal.local, Chrome requests a ticket for HTTP/server1.internal.local — which doesn't match the SPN you registered, so Kerberos fails silently and the browser sends NTLM instead. Disabling CNAME lookup forces Chrome to build the SPN from the URL host, matching your registered SPN exactly.I found that failure by capturing a trace with chrome://net-export/:
# BEFORE — wrong SPN, NTLM fallback
AUTH_LIBRARY_INIT_SEC_CTX → spn = "HTTP/server1.internal.local", mechanism = "NTLM"
# AFTER — correct SPN, Kerberos
AUTH_LIBRARY_INIT_SEC_CTX → spn = "HTTP/auth.example.com", mechanism = "Kerberos" Finally, on a test client run gpupdate /force, restart the browser, and confirm both policies show up in chrome://policy (or edge://policy). If they’re not visible there, they’re not applied — no amount of server-side debugging will help.
Suppressing the NTLM Authentication Prompt
When SPNEGO fails, Chrome on Windows may show an NTLM username/password dialog instead of falling through to the Keycloak login form. The Negotiate scheme bundles Kerberos and NTLM together by design, so there is no server-side way to say “Kerberos only.”
Two workarounds:
- Remove NTLM from Chrome — set
AuthSchemestonegotiateonly (in the same GPO as above). This removes NTLM as a fallback entirely, so if Kerberos fails the browser falls through to the Keycloak form cleanly. - Conditional SPNEGO — use the Keycloak Conditional SPNEGO Authenticator community plugin to skip the Negotiate challenge for requests from external IP ranges, so only internal users see the Kerberos handshake at all.
Troubleshooting
It took me many tries to get this working, so don’t feel bad if you have trouble on the first go — hopefully the troubleshooting below will get you started in the right direction. Please contact me if you’re still struggling or if this guide missed something.
Server-Side
Do a test run of the login page and look in the Keycloak logs:
GSSHeader did not find the right tag
Can also cause no user storage provider that handles kerberos credentials.
The browser sent an NTLM token instead of a Kerberos one.
Things to check:
AuthServerAllowlistset and visible inchrome://policyDisableAuthNegotiateCnameLookupenabled- Verify the SPN:
setspn -Q HTTP/auth.example.com - Kerberos realm is uppercase in Keycloak config
Encryption type RC4 with HMAC is not supported/enabled
The token is Kerberos, but the KDC encrypted it with RC4.
Things to check:
- AES 128 + 256 enabled on the AD service account
- Keytab regenerated with
-crypto AES256-SHA1 krb5.conflists AES inpermitted_enctypes
Cannot find key of appropriate type to decrypt AP-REQ - AES256
The ticket is AES256 but the keytab’s key version doesn’t match what the KDC used. Usually a KVNO (Key Version Number) mismatch after regenerating the account password.
Things to check:
- Regenerate the keytab after enabling AES on the account
- Purge cached tickets on clients:
klist purge - Check KVNO:
Get-ADUser svc-keycloak -Properties msDS-KeyVersionNumber
Not found LDAP user with kerberos principal [User@REALM]
SPNEGO worked — Keycloak extracted the principal but couldn’t find a matching user in LDAP. This is a principal-to-user mapping problem.
Things to check:
- Leave
Kerberos Principal Attributeempty - This tells Keycloak to strip
@REALMand look up bysAMAccountName - Verify the user exists:
Get-ADUser -Filter { sAMAccountName -eq "username" }
Client-Side
klist— verify the client has a Ticket Granting Ticket (TGT)klist get HTTP/auth.example.com— test service ticket acquisition against the KDCklist purge— clear cached tickets after changing AES settings or rotating the keytabchrome://policy— verifyAuthServerAllowlistandDisableAuthNegotiateCnameLookupare appliedchrome://net-export/→ NetLog Viewer — searchAUTH_CONTROLLERfor the SPN, mechanism, and error codes Chrome is seeing
Further Reading
- Keycloak Server Admin Guide — Kerberos — official reference for LDAP federation and Kerberos settings
- Microsoft — Service Principal Names — SPN format, registration, and troubleshooting
- MIT Kerberos — krb5.conf reference — every option in every section
- Chrome Enterprise HTTP authentication policies —
AuthServerAllowlist,DisableAuthNegotiateCnameLookup,AuthSchemes - Keycloak Conditional SPNEGO Authenticator — community plugin for skipping SPNEGO on external IPs
- Service accounts in Active Directory — my notes on managing AD service accounts, including the gotchas around password rotation and delegation