Delegation and impersonation
Four of the protocols iya-sts speaks let one party obtain a token about somebody else:
- the OAuth 2.0 token exchange (RFC 8693);
- WS-Trust’s
OnBehalfOfandActAs(1.3, 1.4); - Kerberos’ two Microsoft extensions, S4U2Self and S4U2Proxy (MS-SFU), and a forwarded ticket-granting ticket;
- GNAP’s trusted client presenting a user assertion, and a resource server deriving a downstream token (RFC 9635, RFC 9767).
None of the four specifications says who may do this. RFC 8693 section 5 leaves it to “the policy of the authorization server”, and WS-Trust describes only what a requester asks for. Active Directory’s answer for Kerberos is a handful of account attributes.
iya-sts answers the question once, for all four protocols:
- the same settings on the same directory entries;
- the same two questions asked of the same XACML issuance policy;
- the same record of every act, issued or refused, on Monitoring → Delegation.
This page describes that model, then what each protocol adds.
The four parties
Every act names four parties. Each protocol finds them in its own message:
| Party | What it is | OAuth 2.0 token exchange | WS-Trust | Kerberos |
|---|---|---|---|---|
| Subject | who the new token is about | the subject_token’s subject |
the subject of the token inside OnBehalfOf / ActAs |
the user named in S4U2Self (PA-FOR-USER or PA-S4U-X509-USER), or the client of S4U2Proxy’s evidence ticket |
| Actor | who is asking | the actor_token’s subject; without one, the authenticated client |
the requester (whoever authenticated the RST) | S4U2Self: the service asking for a ticket to itself; S4U2Proxy: the front end presenting the evidence ticket |
| S (source) | the application the subject’s token was issued for | the subject_token’s aud, else its client_id / azp |
the delegated assertion’s Audience |
the service the evidence ticket was issued for |
| R (target) | the application the new token is for | the one audience or resource |
the AppliesTo |
the service named in the TGS-REQ |
Parties are entries in the directory.
- An application is named by its identifier, client ID or registered
audience,
AppliesToor service principal name. - A person is named by their username.
Every target is resolved to the application that registered it before anything is compared. A target that no application registers is refused.
Delegation, impersonation, and acting for yourself
An act has one of three semantics:
- Delegation. The actor acts for the subject visibly.
- The issued token names the actor: RFC 8693’s
actclaim, or a SAML 2.0 Delegation Restriction condition. - Kerberos S4U2Proxy’s ticket is the front end’s request on the user’s behalf.
actclaims nest. A token that already carried an actor keeps it under the new one (RFC 8693 section 4.1).
- The issued token names the actor: RFC 8693’s
- Impersonation. The actor obtains a token that is simply the subject’s.
- Nothing in the token names the actor.
- A prior
actis still kept. An exchange never turns a delegated token into an ordinary one.
- Self. The actor is the subject, or is S keeping the token for S.
- Nobody is acted for, and nothing beyond the protocol’s own checks is needed.
- A self exchange that names no target is for the subject token’s own audience.
The policy chooses the semantics, by this precedence:
- what the request asks for:
- RFC 8693: this service’s extension parameter
exchange_semantics=delegation|impersonation; - WS-Trust: the element (
ActAsis delegation,OnBehalfOfis impersonation); - Kerberos: the extension (S4U2Self is impersonation, S4U2Proxy is delegation);
- RFC 8693: this service’s extension parameter
- the actor’s default (
appDefaultDelegationSemantics); - the subject’s default (
stsDefaultDelegationSemantics); - the realm’s
delegation.defaultSemantics(delegation unless set).
A request may only ask for semantics that both the actor and the subject allow:
- the actor’s
appDelegationSemantics— empty means delegation only; - the subject’s
stsDelegationSemantics— empty means either.
The common controls
These are the same for all four protocols. They live on the directory
entries, so they can be edited from the console, through /admin-api, or with
an ldapmodify.
On an application entry (Directory → Applications → the application):
| Attribute | Set on | Meaning | Active Directory analogue |
|---|---|---|---|
appAllowedToDelegateTo |
S | Applications S may hand a subject on to. For an impersonation, the applications the actor may reach. | msDS-AllowedToDelegateTo |
appAllowedToActOnBehalfOf |
R | Applications and people R accepts acting for others: resource-based delegation, set by the target’s owner. | msDS-AllowedToActOnBehalfOfOtherIdentity |
appDelegationSemantics |
the actor | Semantics it may use, delegation and/or impersonation. Empty means delegation only. |
TRUSTED_TO_AUTHENTICATE_FOR_DELEGATION (impersonation) |
appDefaultDelegationSemantics |
the actor | Its semantics when the request names none. | — |
appDelegationSubjectGroup |
the actor | Groups, by DN, whose members it may act for. Empty means anybody not protected. | — |
appNotDelegated |
an application as subject | Never acted for. | NOT_DELEGATED |
appMayAct |
an application as subject | The DN of one party it names as its delegate: tokens about it carry may_act naming that party, as the issuance policy assigns it. |
— |
krb5TrustedForDelegation |
a Kerberos service | Kerberos only, off by default. Unconstrained delegation: it may receive and use a user’s forwarded TGT. | TRUSTED_FOR_DELEGATION |
appAllowedProtocol |
any application | Protocols the application is used with. The controls above apply to whichever of WS-Trust, OAuth 2.0, Kerberos and GNAP it lists; there are no per-protocol copies. | — |
On a person’s entry (Directory → People → the person, Delegation):
| Attribute | Meaning |
|---|---|
stsNotDelegated |
Never acted for, by anybody (NOT_DELEGATED). |
stsDelegationSemantics |
Semantics this person may be acted for with. Empty means either. |
stsDefaultDelegationSemantics |
Their default, after the actor’s. |
stsMayAct |
One party this person names as their delegate. Their access tokens (and WS-Trust JWTs) carry RFC 8693 section 4.4’s may_act claim naming it — the issuance policy’s assign-may-act question assigns it, and a realm’s policy may name another party or none. |
Realm settings (Protocols → OAuth 2.0 → Delegation, and GET
/admin-api/config):
| Setting | Environment variable | Default | What it does |
|---|---|---|---|
delegation.defaultSemantics |
STS_DELEGATION_DEFAULT_SEMANTICS |
delegation |
The last word on semantics. |
delegation.protectedGroups |
STS_DELEGATION_PROTECTED_GROUPS |
(none) | Groups whose members are never acted for: Active Directory’s Protected Users. The console’s Admin Read and Admin Write rosters are always protected as well. |
delegation.actorRole |
STS_DELEGATION_ACTOR_ROLE |
DELEGATION_ACTOR |
The role a person needs before they may act for anybody. An application needs no role. |
delegation.maxRecords |
DELEGATION_MAX_RECORDS |
2000 |
How many acts Monitoring → Delegation keeps. |
All four are runtime settings, per realm. This table is a copy: the live
values are on the console page and at GET /admin-api/config.
How an act is decided
The issuance policy refuses in this order. The first refusal that applies is the one the client hears.
may_actnames somebody else. The subject’s token carriesmay_act(RFC 8693 section 4.4) and it does not name the actor. Refused in every mode: the token itself says no.- More than one target. A token is issued for exactly one.
- The target is not registered. No application in the realm registers it.
- No target, unless the act is self.
- The subject is protected:
stsNotDelegatedorappNotDelegated, a member of adelegation.protectedGroupsgroup, or on the console roster. - The actor is unknown, or is a person without the role
delegation.actorRolenames. - The semantics are not allowed by the actor or the subject.
- The subject is outside the actor’s
appDelegationSubjectGroup, unless the subject’smay_actnames the actor. - The subject has no authority. It holds none of the roles the
application requires (
appRequiredRole): S for a delegation, R otherwise. - The relationship does not hold.
- Delegation: S must delegate to R (
appAllowedToDelegateToon S, orappAllowedToActOnBehalfOfon R). In addition, the actor must be S, R, or a party R accepts by name. - Impersonation: the actor must reach R: R is the actor itself, R is
on the actor’s
appAllowedToDelegateTo, or R accepts the actor.
- Delegation: S must delegate to R (
Everything else is allowed, with the semantics chosen above and R as the audience.
Product and development mode
Product mode refuses. Development mode asks the same questions, issues the token anyway, and writes on the act “WOULD HAVE BEEN REFUSED in product: …”, so a client under test still gets a working token and the operator sees what would break.
Two refusals hold in both modes, because they are not policy but the request contradicting itself:
- a
may_actmismatch; - a WS-Trust request carrying both
OnBehalfOfandActAs.
The KDC refuses in both modes, as it always has: in development its fixture accounts carry delegation settings (seeded onto their entries) so that every refusal and every success can be reached, and a KDC that issued a refused ticket would change what goes on the wire. The act on Monitoring → Delegation still says what refused it.
Writing your own rules
The rules are rules of the issuance policy, so a realm can add its own. A
realm’s issuance policy (Directory → Policies → XACML) is asked first;
the built-in policy answers whatever it says nothing about. To refuse more,
write a Deny on action-id exchange-token with the obligation
urn:sts:xacml:obligation:exchange (verdict refuse, refusal policy); the
client then hears its protocol’s policy refusal. The attributes a rule can
test — the four parties, both semantics, may_act, the protocol and the
mode — are listed in XACML.
Per protocol
OAuth 2.0 token exchange (RFC 8693)
grant_type=urn:ietf:params:oauth:grant-type:token-exchange, with asubject_tokenand optionally anactor_token. In product mode each must be a token this realm signed and has not revoked, or an assertion from an issuer the realm declared (below).exchange_semantics=delegation|impersonationis this service’s extension for asking. Any other value, or the parameter sent twice, isinvalid_requestin every mode.- Delegation puts
act: { sub: <actor> }on the access token, nesting anyactthe subject token carried. Impersonation issues a token about the subject with no newact. may_actis read off the verified subject token and compared with the actor’ssub(andiss, when the claim has one). It is issued on a person’s access tokens when their entry carriesstsMayAct.- The scope may narrow, never widen — a rule of the issuance policy too
(
exchange-widens-scope): in product, a scope outside the subject token’sscopeisinvalid_scope. A subject token with noscopeclaim (an ID Token, a WS-Trust JWT) has nothing to compare against. - Refusals are spoken as RFC 8693 section 2.2.2 says:
| Refusal | Error |
|---|---|
| no relationship; two targets; an unregistered target; no target | invalid_target |
the semantics; authority; a protected subject; an unknown actor; may_act; a realm policy |
invalid_request |
In development, two audiences are refused too, by RFC 9068 section 3: an access token for two resources is ambiguous.
See OAuth 2.0 and OpenID Connect and Configuring OAuth 2.0 grants.
Assertions as the subject or the actor
RFC 8693 section 3 names three assertion token types, and each is accepted as
the subject_token or the actor_token when a realm has declared its
issuer:
subject_token_type / actor_token_type |
What it is | Declared on the issuer’s application entry |
|---|---|---|
urn:ietf:params:oauth:token-type:jwt |
an RFC 7523 JWT assertion | oauthAssertionIssuer, with a key as for the JWT bearer grant |
urn:ietf:params:oauth:token-type:saml2 |
an RFC 7522 SAML 2.0 assertion | oauthSamlAssertionIssuer, with a certificate as for the SAML bearer grant |
urn:ietf:params:oauth:token-type:saml1 |
a SAML 1.1 assertion | the same as SAML 2.0 |
A jwt token this realm signed is still read as its own token. Anything else
of those types is verified exactly as the assertion grant verifies it:
- the issuer declared, or a person holding a key pair of their own — who may assert only about themselves;
- the signature against a key or certificate registered for that issuer. For SAML, a certificate that merely chains to the realm’s CA is not enough;
- the certificate’s chain and revocation;
- the expiry and the lifetime ceiling;
- one use, ever, in the same history as the grant. An assertion spent at
the
jwt-bearerorsaml2-bearergrant is refused at the exchange, and the reverse. It is spent only when tokens are issued.
The grant’s on/off switches (oauth2.jwtBearerGrant,
oauth2.saml2BearerGrant) do not apply: an exchange is not the grant.
The subject is the person the assertion names, provisioned as the grant
provisions one. An assertion naming nobody the directory holds is refused
(STS-OAUTH-0798). As the actor_token, the person it names is the actor and
act.sub is their subject. SAML 1.1 is read from its own elements: the
AssertionID, the Issuer attribute, the NameIdentifier in each statement,
the urn:oasis:names:tc:SAML:1.0:cm:bearer confirmation method, and the
<AudienceRestrictionCondition>. A SAML assertion whose version is not the
one its declared type says is refused (STS-OAUTH-0797).
The audience is oauth2.tokenExchangeAudience:
authorization-server(the default) — the grant’s rule. The assertion is addressed to this token endpoint or issuer, and a SAMLRecipientis the token endpoint. S, the application the subject’s token was issued for, is then the exchanging client: an assertion addressed to this server was issued for whoever presents it.any-declared-relying-party— also an application registered in the realm. This is token forwarding: a relying party that was handed an assertion trades it here. S is that relying party, a SAMLRecipientmay be an assertion consumer service registered on the exchanging client (samlAssertionConsumerService), and the act on/admin/delegationsays the input was FORWARDED.
Warning. Under
any-declared-relying-party, every relying party an assertion was issued to can exchange it for a token from this service. Turn it on only where that is the design.
An audience the rule does not accept is invalid_request (STS-OAUTH-0796).
Who may then exchange the assertion is the policy above, unchanged: S, the
actor, R and the subject’s protections, as for any subject token.
In development mode an undeclared or unverifiable jwt token is still read
without verifying, as it always was. A SAML token that does not verify is
refused in both modes: there is no unverified reading of XML to fall back on.
WS-Trust OnBehalfOf and ActAs
- The requester is the actor; the token in the element is the subject’s;
its
Audienceis S; theAppliesTois R. ActAsasks for delegation (WS-Trust 1.4 section 9.3). The issued token names the requester after any party the delegated assertion already named:- a SAML 2.0 assertion carries the
SAML V2.0 Condition for Delegation Restriction:
one
del:Delegateper party, least to most recent, as that profile orders them; - a JWT (
TokenTypeurn:ietf:params:oauth:token-type:jwt) carries the same chain as nestedactclaims, the most recent outermost.
- a SAML 2.0 assertion carries the
SAML V2.0 Condition for Delegation Restriction:
one
OnBehalfOfasks for impersonation (1.3 section 9.2). The token is the subject’s, and adds nobody to the chain; one the delegated assertion already carried is kept.- Both elements in one request are refused with
wst:InvalidRequest, in every mode. - A request with no
AppliesTois refused unless it is a self one. - Every policy refusal is a SOAP Fault carrying WS-Trust 1.4 section 11’s
wst:RequestFailed: thefaultcodeon SOAP 1.1, theSubcodeon SOAP 1.2. The fault’s reason says which rule refused. may_actis not read here: the delegated token is always a SAML assertion, which has no such claim. A JWT issued about a person who setstsMayActcarriesmay_act, as an access token does.
See WS-Trust.
Kerberos (MS-SFU)
Kerberos service accounts are applications here: the entry whose identifier
is the service’s SPN@REALM. Their delegation settings are the common ones
above, not a second set kept by the KDC. In development the fixture services’
rules (HTTP/frontend to HTTP/backend, the resource-based HTTP/rbcd, the
HTTP/notrusted that may not impersonate, the trusted HTTP/web) are seeded
onto their entries the first time the KDC is asked, filling only what an entry
does not already hold, and the person sensitive carries stsNotDelegated.
- S4U2Self (protocol transition) is impersonation:
- the service asks for a ticket to itself for a user, named by
PA-FOR-USERor by certificate withPA-S4U-X509-USER; - the actor is that service, and it is R as well;
- it is never refused for want of a policy — a ticket to yourself is not
the privilege — but it is forwardable only when the policy allows the
impersonation: the service allows it (
appDelegationSemantics, the job of Active Directory’sTRUSTED_TO_AUTHENTICATE_FOR_DELEGATION) and the user is not protected; - PA-S4U-X509-USER names the user by name, by a certificate this realm
issued to them (its TLS client or enrollment Issuing CA,
clientAuth, oneurn:sts:person:name, not revoked), or both, which must agree. Its checksum and the request’s nonce are checked, and the reply carries it back with a checksum of its own (key usage 27 when the client asks).
- the service asks for a ticket to itself for a user, named by
- S4U2Proxy is delegation:
- the front end presents the user’s evidence ticket and asks for a ticket to the back end;
- the actor is the front end, which is S;
- classic constrained delegation is the front end’s
appAllowedToDelegateTonaming the back end, and needs a forwardable evidence ticket; - resource-based constrained delegation (the request carries
PA-PAC-OPTIONSwith the RBCD bit) is the back end’sappAllowedToActOnBehalfOfnaming the front end.
- Protected users (
delegation.protectedGroups,stsNotDelegated) are never the subject of S4U, and their tickets are not forwardable. - The evidence ticket is not taken on trust. It is encrypted in the front end’s own key, so the front end could set its forwardable flag (CVE-2020-17049, Bronze Bit) or forge one for anybody. The KDC verifies the PAC’s ticket signature — which covers the flags — and its KDC signature with the krbtgt key, and refuses an evidence ticket with no PAC.
- Unconstrained delegation is
krb5TrustedForDelegationon the service’s entry, off unless set: its tickets carryok-as-delegate, which tells a client it may forward its TGT there. The KDC is never told where a forwarded TGT goes, so what it controls is the person: a protected person’s TGT is not forwardable, and a forwarding request presenting one is refused. - Refusals are the KDC’s own errors:
KDC_ERR_BADOPTIONfor a relationship that does not hold,KRB_AP_ERR_MODIFIEDfor evidence that does not verify,KDC_ERR_POLICYfor the rest of the policy. - Delegation across Kerberos realms and trusts is not yet decided by this policy; see issue #430.
See Kerberos and SPNEGO.
GNAP (RFC 9635, RFC 9767)
- A trusted client presenting a user assertion is impersonation — the
S4U2Self shape. A client whose entry carries
gnapSkipInteractionand that presents a verifiedid_tokenorsaml2assertion in the grant request’suseris issued tokens for that person with nobody asked:- the actor is the client, the subject the person the assertion names;
- there is no S;
- R is each resource server the requested access rights resolve to (one
question each), or the client itself when they name none — which still
needs
impersonationin the client’sappDelegationSemantics, because the token can be presented; - a
may_actclaim in the ID Token must name the client (every mode). The token is the person’s and names no actor.
- Token derivation is delegation — the S4U2Proxy shape. A resource server
presenting
existing_access_token(RFC 9767 section 4):- is the actor and S; the subject is the original token’s person;
- R is each downstream resource server the requested rights resolve to, or the deriving one itself when it only narrows (self);
- the derived token carries no more access than the original, in every
mode, and names the deriving resource server in an
actchain — in all five token formats — capped bygnap.maxDerivationDepth.
- A client skipping interaction without an assertion acts for nobody and is asked nothing.
- Refusals are
request_denied(HTTP 403); the audited code says which rule (STS-GNAP-0770to0782).
See GNAP.
Watching it
- Monitoring → Delegation (
/admin/delegation) lists every act, issued or refused, from all four protocols, with the four parties, the semantics, the tokens consumed and produced, and the sentence saying what allowed or refused it. Filter by protocol, semantics, outcome or any name. In development, a refused act reads WOULD HAVE BEEN REFUSED. - The picture (
/admin/delegation/map) draws the same acts as a diagram:- a box per party and a line per relationship;
- chains across protocols join where they share an application;
- drill-downs per chain, per application and per person.
- Who may act for whom (the page’s policy section) lists the configured relationships, actors with their semantics and subject groups, and protected people and groups.
- The same three are
GET /admin-api/delegation,GET /admin-api/delegation/policyand the map’s?format=json.
Related
- XACML 3.0 and ALFA: the issuance policy these rules are part of.
- Applications: where the application attributes are edited.
- What is not checked: the development-mode behaviour.
- Error codes: each refusal’s
STS-OAUTH-,STS-WSTRUST-andSTS-KRB5-code, recorded on the audit row and never sent.