[Bug] ITokenAcquisition.GetAccessTokenForUserAsync() fails when using ADFS on Windows Server 2019
Which version of Microsoft Identity Web are you using? Microsoft.Identity.Web 1.23.0
Where is the issue? Using sample project 1-1-MyOrg as a template with AddMicrosoftIdentityWebApp() configured for on-prem Windows Server 2019 ADFS's OAuth services, calling ITokenAcquisition.GetAccessTokenForUserAsync() fails. However, when configured to use AzureAD, the token is successfully returned.
- Web app
- [x] Sign-in users
- [x] Sign-in users and call web APIs
- Web API
- [ ] Protected web APIs (validating tokens)
- [ ] Protected web APIs (validating scopes)
- [ ] Protected web APIs call downstream web APIs
- Token cache serialization
- [x] In-memory caches
- [ ] Session caches
- [ ] Distributed caches
- Other (please describe)
Is this a new or an existing app? This is a new test app.
Repro
startup.cs:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(configureMicrosoftIdentityOptions: options =>
{
Configuration.Bind("AzureAd", options);
})
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { "openid" })
.AddInMemoryTokenCaches();
HomeController.cs:
string bearerToken = await _tokenAcquisition.GetAccessTokenForUserAsync(scopes: scopes, user: User);
appsettings.json snippet:
"AzureAd": {
"Instance": "https://myadfs.mydomain.com/adfs/",
"Domain": "",
"TenantId": "mytenant",
"ClientId": "2b80ad5d-daec-481f-9bfc-5912b671e7c5",
"ClientSecret": "mysupersecrectclientsecret",
"MetadataAddress": "https://myadfs.mydomain.com/adfs/.well-known/openid-configuration",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath": "/signout-callback-oidc"
}
Expected behavior I expect to be able to use Microsoft Identity Web .NET with an on-prem Windows Server 2019 ADFS to authenticate accounts and create access tokens. I expect calling ITokenAcquisition.GetAccessTokenForUserAsync() to return an access token as it does when I change my configuration to use AzureAD and a registered web app.
Actual behavior Using on-prem Windows Server 2019 ADFS OAuth, after successful authentication, calling ITokenAcquisition.GetAccessTokenForUserAsync() fails with:
Microsoft.Identity.Web.TokenAcquisition: Information: [MsIdWeb] An error occured during token acquisition: No account or login hint was passed to the AcquireTokenSilent call.
MSAL.NetCore.4.41.0.0.MsalUiRequiredException:
ErrorCode: user_null
Microsoft.Identity.Client.MsalUiRequiredException: No account or login hint was passed to the AcquireTokenSilent call.
Possible solution After spending a few days trying to get this to work, it seems like a similar issue has come up before with ADFS and the MSAL JS libraries. ADFS 2019 Support #1668
When comparing the OAuth http messages between AzureAD and my on-prem ADFS, I can see that AzureAD returns "code" and "client_info". When decoded, "client_info" contains a "uid" and "utid". ADFS will only return "code" and does not return "client_info". AFAIK, there is no way to configure ADFS to return "client_info".
When looking at Microsoft.Identity.Web/TokenAcquisition.cs and the AddAccountToCacheFromAuthorizationCodeAsync method, it expects "client_info". I am guessing this is why it works with AzureAD and not my on-prem Server 2019 ADFS.
The fix for the MSAL JS code was to have ADFS also return the "id_token" along with "code". The JS code then appears to create a "client_info" and/or "uid" and "utid" from the ID token.
I successfully provided a handler for options.Events.OnRedirectToIdentityProvider that modifies the RequestType to "code id_token". With this change, ADFS now posts both "code" and "id_token" to the "signin-oidc" page.
Would it be possible to fix this by updating the code to extract the uid and utid from the ID token as it does in the MSAL JS code.
Additional context / logs / screenshots / link to code When configured to use my AzureAD, the code works as expected. When using ADFS, the signon works perfectly, it's only this step of trying to obtain an access token. When I look at my injected ITokenAcquisition instance, I can see the underlying cache contains a token when using AzureAD, but is empty when configured to use my ADFS. In fact, it appears that most times the ITokenAcquisition instance does not have an attached _application member variable. Meaning it's null.
Stepping through the Microsoft.Identity.Web code, I can confirm that the 'uid' and utid' are null. This prevents retrieval of the account (IAccount) and causes GetAuthenticationResultForWebAppWithAccountFromCacheAsync() to throw a MsalUiRequiredException with the "No account or login hint was passed to the AcquireTokenSilent call."
Here's a snippet from ClaimsPrincipalExtensions.cs:



I have been debugging this a bit more and found that the user token cache does contain an access token, BUT the key for the user account is not the "uid.utid". It has been stored using the "nameidentifier" claim value. Adding the following to GetAuthenticationResultForWebAppWithAccountFromCacheAsync() in TokenAcquisition.cs allows me to successfully retrieve an access token.

@youngj7 : indeed, Microsoft.Identity.Web is not supposed to work with ADFS yet. We have a feature request for it: https://github.com/AzureAD/microsoft-identity-web/issues/852
What is the nameidentifier? is it human readable or is it an a computer intended series of letters and digits?
@jmprieur I will have to double-check, but I think the "nameidentifier" is being pulled from the id_token's "sub" claim. If you look at the bottom-right corner of my last code snippet, you'll see that "AccountId" is a series of letters and digits. The "AccountId" is the same value as the "nameidentifier" claim which matches the id_token's "sub" claim returned from ADFS2019.
You mentioned, "Microsoft.Identity.Web is not supposed to work with ADFS yet". I might be a bit confused as the documentation page Active Directory Federation Services support in MSAL.NET indicates "MSAL.NET supports connecting to AD FS 2019, which is Open ID Connect compliant and understands PKCE and scopes." Is MSAL.NET different from "Microsoft.Identity.Web"? Should I be using a different set of .NET calls?
It seems like the current implementation is so close to working with ADFS2019. If this is not a bug, do you know when support for ADFS2019 falls into your timeline? We are using MSAL.js with Angular and ADFS 2019 successfully and were hoping to use this user friendly library for our C# based code.
@jmprieur Since the online documentation indicates this should be working, is this a bug or does the documentation need to be corrected?
@youngj7 : which documentation. Do you have a link? I don't remember us writing this at all (and there is a feature request). @jennyf19 does it ring any bells?
@jmprieur The link to the Microsoft document is in my previous comment above.
@youngj7 : I'm confused. this doc mentions MSAL.NET, not Microsoft.Identity.Web?
@jmprieur I am obviously confused too. Earlier, I asked if MSAL.NET was different than Microsoft.Identity.Web. I see now that MSAL.NET pages link to Microsoft.Identity.Client, but they are all intertwined. I thought these libraries were all under a generic umbrella called "MSAL". Should I be integrating with Microsoft.Identity.Client in my ASP.NET Core web application instead of Microsoft.Identity.Web?
No, you are doing the right thing, @youngj7. I'm just saying that MSAl.NET supports it, but Id.Web not yet. they are different libraries of the same family, and so far nobody had requested it. But I agree we are close
@jennyf19 : let's try to squeeze in the next release
@jmprieur, I am working for Utilities companies located in Belgium (Elia) and Germany (50 Hzt). We have a lot of services using ADFS for the authentication. At Ignite it was mentioned during a session that ADAL will be end of support and that we have to move to MSAL with ADFS 2019. We have done this and we are preparing the migration for .NET 5+ applications (using still ADAL for the moment). We are facing the same issue reported here. If you can't add the change because you are not supporting ADFS, can you give us an example for .NET 6 to retrieve the access token with MSAL from ADFS 2019. At least to support the business customers.
Thanks.
@kalyankrishna1 : do you have ADFS samples?
@GFlisch have a look at AD FS to Azure AD application migration playbook for developers. Its slightly dated, but should provide a good idea of steps required to migrate
Hi @kalyankrishna1 , thanks I will try. Do you think that the standard Microsoft.IdentityModel.Protocols.OpenIdConnect package could be used with ADFS 2019? I was thinking about this and wanted to test it also. But if you know already that ADFS 2019 is not compliant enough with the standard OIDC I will not tried this....
@GFlisch, sorry, I dont know for sure. I'd take that question to the library's github page