Reputation: 105
Working on a proof of concept that involves an ASP.NET Core 2.1 web app using MSAL to authenticate to AD FS 2019 (v5.x) via the OAuth 2.0 Client Credentials grant type in order to retrieve an access token that the ASP.NET Core web app will subsequently use to call an in-house web API.
Authentication is working great, but we want to have AD FS pass a claim that it receives in the client assertion JWT back through in the resulting access token (also a JWT) that AD FS generates and sends back to the client.
An example of a signed client assertion that we construct and send to AD FS (via the use of MSAL v4.6.0, using the WithClientAssertion()
method of ConfidentialClientApplicationBuilder
) looks like this (when decoded - some details changed for privacy reasons):
Header:
{
"alg": "RS256",
"kid": "D15B382A913303060B26CDF497F3083522A3ADE3",
"typ": "JWT",
"x5t": "0Vs4KpEzAwYLJs30l_MINSKjreM"
}
Payload:
{
"aud": "https://our.internal.adfs.host/adfs/oauth2/token",
"iss": "2324fca8-c413-460f-9658-113d124ca2d1",
"jti": "456f9e07-bae4-442d-83f5-a10f0d0bc7d6",
"sub": "2324fca8-c413-460f-9658-113d124ca2d1",
"custom_claim": "Custom value.",
"exp": 1578875907,
"iat": 1578872307,
"nbf": 1578872307
}
Note the "custom_claim" claim that we have added to the JWT client assertion. On the ADFS side, we have configured a claim description for this custom claim and set a custom claim pass through rule that results in this Claims Rule Language code:
c:[Type == "http://some.com/custom_claim"]
=> issue(claim = c);
Just to be sure, we also have another rule that I gather should pass through all claims, regardless of type:
x:[]
=> issue(claim = x);
And we have another rule that rather than attempting to pass through an incoming claim, just gets AD FS to generate a hardcoded "role" claim from scratch:
=> issue(Type = "http://schemas.microsoft.com/ws/2008/06/identity/claims/role", Value = "WebApiRole");
Note that we are using "Application Groups" in AD FS.
Authentication works and the access token received back from AD FS looks like this (when decoded - some details changed for privacy reasons):
Header:
{
"typ": "JWT",
"alg": "RS256",
"x5t": "AZxyHfwAdI_vOhixPV7ocphCprk",
"kid": "AZxyHfwAdI_vOhixPV7ocphCprk"
}
Payload:
{
"aud": "https://our.internal.webservice.host",
"iss": "http://our.internal.adfs.host/adfs/services/trust",
"iat": 1578873409,
"nbf": 1578873409,
"exp": 1578877009,
"role": "WebApiRole",
"auth_time": "2020-01-12T23:56:49.006Z",
"authmethod": [
"http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/x509",
"http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/tlsclient"
],
"sub": "2324fca8-c413-460f-9658-113d124ca2d1",
"anchor": "sub",
"appid": "2324fca8-c413-460f-9658-113d124ca2d1",
"apptype": "Confidential",
"endpointpath": "/adfs/oauth2/token/",
"insidecorpnetwork": "true",
"clientreqid": "605dae38-c19a-4958-813c-dbc961ae9fc0",
"clientip": "x.x.x.x",
"userip": "x.x.x.x",
"ver": "1.0",
"scp": ".default"
}
Note that we are using certificate-based authentication, hence the "http://schemas.microsoft.com/ws/2008/06/identity/authenticationmethod/x509" value in the "authmethod" collection, not that I think that has any bearing on our problem. Also note that the "role" claim has been added as expected.
The current issue is that no matter what we do, we do not appear to be able to get AD FS to pass through any claims from the incoming client assertion out to the resulting access token. We can get AD FS to generate a new claim and add it to the outgoing access token (as with the "role" claim above) but pass through does not seem to be working for us.
Can anyone suggest something we may be missing here?
Upvotes: 1
Views: 2458
Reputation: 151
This is a well known limitation in ADFS. Other token issuers, like for example PingFederate, can easily support this type of use case. There are ways to pass in values to ADFS via HTTP request headers that can be issued in the outgoing token but they are a hack at best. See https://learn.microsoft.com/en-us/windows-server/identity/ad-fs/operations/ad-fs-claims-types
Upvotes: 1
Reputation: 46803
The way claims are designed, they get values from an identity repository or are derived from another IDP upon authentication and then passed through to the client.
You can't send a claim from a client and expect ADFS (or any other IDP) to return it.
If you want to round trip something, you could use wctx in WS-Fed or the OIDC nonce.
Update
Imagine this scenario:
Client - ADFS - Another IDP
So the other IDP is a CP on ADFS. The client selects to authenticate on this IDP. A token is returned to ADFS with IDP claims. To pass these claims to the client, you use a pass-through rule i.e. pass these claims through ADFS to the client.
ADFS can build a token containing both claims from the IDP and from AD.
Upvotes: 0