Kerberos Constrained Delegation Not Working
Constrained delegation almost always breaks in the same place — a middle-tier service that is not actually trusted to hand off a client’s identity to the back-end it is trying to reach — and the KDC tells you so bluntly with KDC_ERR_BADOPTION.
The KDC’s refusal when a client requests a service ticket the requesting account isn’t allowed to delegate to.
The attribute holding the exact list of SPNs a front-end account may delegate to. Empty or wrong, and delegation fails.
The optional “use any authentication protocol” mode that lets a service delegate for a user it authenticated by non-Kerberos means.
Introduction
A web application authenticates a user fine on its own front door, but the moment it tries to query a back-end SQL Server or file share on that user’s behalf, everything the user does comes back as if it were run by the application’s own service account instead. Or worse — the middle tier just throws an authentication exception the instant it tries the second hop. Nothing about the user’s password or group membership is wrong; the double-hop itself is being refused.
This is constrained delegation failing to do the one thing it exists for: letting service A present a specific user’s identity to service B, and only service B. Once you accept that the KDC is enforcing an explicit allow-list rather than something being broken in Active Directory generally, the fix narrows quickly to a short set of account-level settings.
What You’re Seeing
The error surfaces most clearly in a network capture of the TGS-REQ the middle tier sends when it tries to obtain a service ticket to the back-end on the user’s behalf. Microsoft Network Monitor labels the response plainly:
# Network Monitor trace, filtered on KerberosV5
KerberosV5:TGS Request Cname: middletier$ Realm: CORP.EXAMPLE.COM Sname: http/backend01.corp.example.com
KerberosV5:KRB_ERROR KDC_ERR_BADOPTION
Above the protocol layer, the symptom is usually plainer and less specific: the middle-tier application logs a generic Kerberos or “access denied” exception the instant it attempts the second hop, while direct authentication to the front-end works normally. If the back-end is SQL Server, this typically shows up as the connection succeeding but running under the application’s own service identity rather than the calling user’s — or an outright login failure if the back-end has no fallback path.
What It Actually Means
When a client authenticates to a front-end service, that service can request a service ticket to a second, back-end service on the client’s behalf — but only if Active Directory says it is allowed to. Constrained delegation encodes that permission as an explicit list: the msDS-AllowedToDelegateTo attribute on the front-end’s account (its computer account, or a dedicated service account) holds the exact SPNs of the back-end services it may present credentials to. When the front-end asks the KDC for a ticket to a service that is not on that list — or asks at all when no list exists — the KDC returns KDC_ERR_BADOPTION rather than silently degrading to some lesser form of access.
Two further account-level switches change whether the request even gets that far. TrustedForDelegation governs unconstrained delegation and is unrelated to the constrained case. TrustedToAuthForDelegation enables protocol transition — “Use any authentication protocol” in the GUI — which lets the front-end delegate for a user it did not itself authenticate via Kerberos (for example, a user who logged on with forms-based auth). Without it, the front-end must have obtained the user’s own Kerberos ticket first (“Use Kerberos only”). Separately, an account with AccountNotDelegated set, or a member of the Protected Users group, cannot be delegated for under any configuration — that flag exists specifically to make an account immune to delegation, so if it is set on the user (not the service), no amount of front-end configuration will fix the error.
How to Diagnose
Work from the account configuration outward before reaching for a network trace — most delegation failures are visible from PowerShell alone.
# 1. Confirm the front-end and back-end are in the same domain (constrained
# delegation does not work across domains or forests - RBCD is required there)
Get-ADComputer -Identity MIDDLETIER01 -Properties CanonicalName
Get-ADComputer -Identity BACKEND01 -Properties CanonicalName
# 2. Check the front-end account's delegation configuration - computer account case
Get-ADComputer -Identity MIDDLETIER01 -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo |
Select-Object Name, TrustedToAuthForDelegation, msDS-AllowedToDelegateTo
# 3. Same check for a custom service account running the front-end service
Get-ADUser -Identity svc-middletier -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo, AccountNotDelegated |
Select-Object Name, TrustedToAuthForDelegation, msDS-AllowedToDelegateTo, AccountNotDelegated
# 4. Confirm the calling user isn't explicitly blocked from being delegated
Get-ADUser -Identity jdoe -Properties AccountNotDelegated, MemberOf |
Select-Object Name, AccountNotDelegated
# 5. Confirm a KDC is reachable at all before assuming this is a delegation problem
nltest /dsgetdc:corp.example.com /force /kdc
# 6. Inspect the tickets actually held by the process/session hitting the error
klist tickets
$null msDS-AllowedToDelegateTo on the front-end account is the single most common finding here. If that attribute has never been set, the front-end has no constrained delegation permissions at all, regardless of anything configured on the back-end.
Common Causes
| Cause | How to Confirm | Fix |
|---|---|---|
| Back-end SPN missing from the front-end’s allow-list | msDS-AllowedToDelegateTo on the front-end account doesn’t include the exact SPN the middle tier is requesting (e.g. HOST/backend01.corp.example.com). |
Add the missing SPN to the allow-list: Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}. |
| Delegation never enabled on the front-end account | The Delegation tab is absent or set to “Do not trust this computer/user for delegation” in Active Directory Users and Computers. | Select Trust this user for delegation to specified services only and add the back-end SPN, or set msDS-AllowedToDelegateTo directly via PowerShell. |
| Front-end authenticated the user by a non-Kerberos method (forms auth, certificates) but protocol transition isn’t enabled | TrustedToAuthForDelegation is False on the front-end account while the app relies on “Use any authentication protocol”. |
Enable protocol transition on the front-end account: Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true. |
| Calling user account is exempt from delegation | Get-ADUser jdoe -Properties AccountNotDelegated returns True, or the user is a member of Protected Users. |
This is by design for high-privilege accounts — do not clear it to work around the error. Use a different, lower-privilege account for the delegated path, or redesign the access pattern. |
| Front-end and back-end are in different domains or forests | Constrained delegation SPN checks fail even though the list looks correct — the topology itself doesn’t support classic KCD. | Switch to resource-based constrained delegation (RBCD) using -PrincipalsAllowedToDelegateToAccount on the back-end account instead. |
| Custom service account is missing the matching computer-account delegation entry | The service account’s own delegation list looks correct, but the front-end server’s computer account has no delegation configured at all. | Configure Trust this computer for delegation to specified services only (with Use any authentication protocol) on the front-end server’s computer account, in addition to the service account. |
| SPN registered on the wrong account, or duplicated | setspn -Q HOST/backend01.corp.example.com shows it registered to an account other than the one the back-end service actually runs as, or to more than one account. |
Remove the duplicate/incorrect registration with setspn -D, then register it against the correct back-end service account. |
Working Through a Fix
Start by establishing which kind of front-end account you have — a computer account running the service under a built-in identity (Network Service, Local System) or a dedicated service account — because the configuration lives on different objects in each case. For a computer account, both the delegation configuration and the SPN list live on the same object:
# View current state before changing anything
Get-ADComputer -Identity MIDDLETIER01 -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo
# Enable constrained delegation with protocol transition to the back-end HOST SPN
Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true
Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}
For a custom service account, the delegation permission goes on the service account itself, but the front-end server’s own computer account also needs delegation enabled — a step that’s easy to miss because it feels redundant when the service account already has it configured:
# Service account: enable delegation and the allow-list
Set-ADAccountControl -Identity svc-middletier -TrustedToAuthForDelegation $true
Set-ADComputer -Identity MIDDLETIER01 -Add @{'msDS-AllowedToDelegateTo'=@('HOST/backend01.corp.example.com')}
# Front-end computer account: also needs "Trust this computer for delegation
# to specified services only" with "Use any authentication protocol"
Set-ADComputer -Identity MIDDLETIER01 -TrustedForDelegation $false
Set-ADAccountControl -Identity MIDDLETIER01 -TrustedToAuthForDelegation $true
If the front-end and back-end genuinely sit in different domains or forests, none of the above will ever succeed — classic constrained delegation is restricted to a single domain by design. The supported path there is resource-based constrained delegation, configured from the back-end side instead:
# On the back-end account, allow the front-end's computer account to
# delegate to it - this is configured on BACKEND01, not MIDDLETIER01
Set-ADComputer -Identity BACKEND01 -PrincipalsAllowedToDelegateToAccount MIDDLETIER01$
How to Prevent It
Keep the msDS-AllowedToDelegateTo list as narrow as the application actually needs — one or two specific back-end SPNs, never a broad set added “just in case.” A wide delegation list is exactly the kind of lateral-movement path an attacker who compromises the front-end account will look for, so treat it with the same review discipline as a firewall rule change. Document which account holds delegation rights and to what, since this configuration is invisible from the application side and tends to be forgotten until the next migration breaks it.
When standing up a new middle tier, confirm the topology (same domain vs cross-domain) before choosing constrained delegation over RBCD — picking the wrong one early causes exactly this error weeks later when someone finally exercises the double-hop path in production. For the protocol fundamentals this all sits on top of, see the Kerberos authentication post; if the failure turns out to be about ticket decryption rather than delegation permissions, that’s the separate duplicate SPN failure mode.
Quick Reference
| Command | Use |
|---|---|
Get-ADComputer -Identity <name> -Properties msDS-AllowedToDelegateTo | List the SPNs a computer account is currently allowed to delegate to. |
Set-ADComputer -Identity <name> -Add @{'msDS-AllowedToDelegateTo'=@('HOST/<target>')} | Add a back-end SPN to the allow-list. |
Set-ADAccountControl -Identity <name> -TrustedToAuthForDelegation $true | Enable protocol transition (“Use any authentication protocol”). |
Get-ADUser -Identity <name> -Properties AccountNotDelegated | Check whether a user account is explicitly exempt from delegation. |
Set-ADComputer -Identity <backend> -PrincipalsAllowedToDelegateToAccount <frontend>$ | Configure resource-based constrained delegation (cross-domain safe). |
setspn -Q <SPN> | Check which account an SPN is registered against, and whether it’s duplicated. |
| KDC_ERR_BADOPTION | The KDC rejected a TGS request because the requesting account isn’t permitted to delegate to the target SPN. |
Final Thoughts
KDC_ERR_BADOPTION reads as cryptic the first time you hit it, but it is the KDC doing exactly its job: refusing a delegation request that was never explicitly granted. The fix is rarely about Kerberos internals — it’s confirming which account is actually acting as the front-end, and whether its allow-list names the back-end service you expect.
Check the account configuration before reaching for a network trace, keep the allow-list narrow, and remember that an account marked non-delegable is a security control working as intended, not a bug to route around.
Get-ADComputer -Identity <frontend> -Properties TrustedToAuthForDelegation, msDS-AllowedToDelegateTo first. An empty or missing back-end SPN in that list is the most common cause of KDC_ERR_BADOPTION; add it with Set-ADComputer -Add, enable TrustedToAuthForDelegation if the app needs protocol transition, and confirm the calling user isn’t flagged AccountNotDelegated before assuming the fix worked.
Next: when Kerberos authentication fails for a reason that looks similar but isn’t — ticket decryption breaking under KRB_AP_ERR_MODIFIED because of a duplicate SPN.