Errors when upgrading ADAL from 2.22 to 5.0.5 - azure-active-directory

I have a WPF client using ADAL to get tokens from ADFS to call our web-api. It uses the currently logged on windows user and authenticates against ADFS without any logon screen or prompts. However, after upgrading from 2.22.302111727 to 5.0.5 I'm having issues getting this to work. I want to upgrade to be able to target .NET Standard in my authentication library.
The existing code with 2.22 was
var ac = new AuthenticationContext(authority, false);
var token = ac.AcquireToken(resourceURI, clientId, new Uri(clientReturnUri), PromptBehavior.Never);
When upgrading to 5.0.5 the API has changed slightly and I followed the documentation for acquiring tokens silently using Integrated authentication on Windows (Kerberos) which results in
var ac = new AuthenticationContext(authority, false);
var token = await ac.AcquireTokenAsync(resourceURI, clientId, new UserCredential());
this results in exception
There was an error deserializing the object of type Microsoft.IdentyModel.Clients.ActiveDirectory.Internal.Oauth2.TokenResponse. Encountered unexpected character '>'.
which suggests that some HTML error message was returned.
And in the event log on the ADFS Domain Controller I see this:
Microsoft.IdentityServer.RequestFailedException: MSIS7065: There are no registered protocol handlers on path /adfs/oauth2/token to process the incoming request.
I tried changing the code a bit to be more similar to the code I used for 2.22:
var ac = new AuthenticationContext(authority, false);
var token = await ac.AcquireTokenAsync(resourceURI,clientId,new Uri(clientReturnUri),new PlatformParameters(PromptBehavior.Never));
This gave me exception
The browser based authentication dialog failed to complete. Reason: The server or proxy was not found.
But, with this code I can see in the event log that a token was successfully created:
A token of type 'http://schemas.microsoft.com/ws/2006/05/identitymodel/tokens/Kerberos' for relying party 'http://XXXXX.XXXXX.com/adfs/services/trust' was successfully authenticated. See audit 501 with the same Instance ID for caller identity.
and
An OAuth access token was successfully issued to client 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX' for the relying party 'http://XXXXXX/'. See audit 500 with the same Instance ID for issued claims. See audit 501 with the same Instance ID for caller identity.
What has changed in the way ADAL performs the authentication with Kerberos?
How can I make ADAL 5.0.5 do what 2.22 did sucessfully?

Related

How do you handle authentication and token refresh with Microsoft Identity and Azure AD

I'm attempting to secure a .Net 6.0 / Razor Page web application against Azure AD. I was able to complete the application registration with Azure AD and successfully authenticate users. The issue I'm facing occurs when the issued token expires. I have some experience working with Angular and IdentityServer implementations, but Razor Page/Microsoft Identity is still new to me.
What I would like to happen:
The user logs in with their Microsoft account
The user's session is uninterrupted for up to 12 hours (with all token management happening behind the scenes)
After 12 hours the session/cookies will expire and the user will need to log in again
What is happening:
The user logs in and is authenticated
After approximately one hour, the application triggers a call to the /authorize endpoint the next time the user takes any action (such as trying to navigate to a new page)
This causes the application to reload on the page the user was currently on (thus interrupting their experience)
Additional Issue: I am also receiving a CORS error under similar circumstances as above. The difference here is this is occurring when the user is in the middle of form data entry when the (presumed) token expiration occurs. When they click submit to post the form, a 302 xhr / Redirect to the /authorize endpoint is triggered. This call results in a CORS error. Refreshing the page is required to trigger a successful call (and they need to start over on their form). Update: This is occurring due to an AJAX call (nothing to do with the form/post specifically). See the edit at the end.
Ideally, I would like the token to be automatically (and silently) refreshed via a refresh token once it is nearing expiration. I would also, of course, like to avoid the scenario of the CORS error when they are attempting to post when the token has expired.
Some code snippets (note: I'm manually adding authentication to an existing app, I did not use any scaffolding/templates for the initial project creation).
Note: I initially tried the below implementation without defining custom authOptions, but during debugging and different attempts at resolution, it exists in the below state. Results were consistent either way.
Program.cs
var builder = WebApplication.CreateBuilder(args);
var config = builder.Configuration;
var services = builder.Services;
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(
authOptions =>
{
config.Bind("AzureAD", authOptions);
authOptions.MaxAge = TimeSpan.FromHours(12);
authOptions.SaveTokens = true;
},
sessionOptions =>
{
sessionOptions.Cookie.MaxAge = TimeSpan.FromHours(12);
sessionOptions.Cookie.Name = "Custom-Cookie-Name";
sessionOptions.ExpireTimeSpan = TimeSpan.FromHours(12);
sessionOptions.SlidingExpiration = false;
})
.EnableTokenAcquisitionToCallDownstreamApi(config.GetValue<string>("GraphApi:Scopes")?.Split(' '))
.AddMicrosoftGraph(config.GetSection("GraphApi"))
.AddSessionTokenCaches();
services.AddRazorPages(options =>
{
options.Conventions.AddPageRoute("/Disclaimer", "/");
})
.AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddHttpContextAccessor();
........
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseSession();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
});
app.UseSaveUserDetailsOnAuthentication();
app.UseIdentityPageInitialization();
app.MapRazorPages();
app.MapControllers();
app.Run();
I also have some middleware that is using the graph service to hit the /me endpoint and store some user details under specific conditions (in case this is relevant):
Graph Middleware
public async Task InvokeAsync(HttpContext context, UserManager<ApplicationUser> userManager, GraphServiceClient graphServiceClient)
{
var page = context.GetRouteValue("page")?.ToString();
if (!page.IsNullOrEmpty() && page.Equals("/Disclaimer") && context.User.Identity?.IsAuthenticated == true)
{
var user = await graphServiceClient.Me
.Request()
.GetAsync()
.ConfigureAwait(false);
The below snippet is what occurs when attempting the post scenario above.
The tl/dr questions are, using the Microsoft Identity libray/MSAL, how do I:
Silently refresh a user's token
Avoid reloading the page to get a new token (i.e.: calling /authorize and redirecting to obtain a new token)
Handle token expiration from the client-side (avoid the CORS error when posting a form). Do I need to add an additionally client-side js library to manage this?
I've tried scouring Microsoft's documentation, but nothing I've found goes into detail on this. The closest I found was MSAL's documentation mentioning that it handles token refresh for you (but it seemingly isn't happening in my case).
I'm expecting that the token will be silently refreshed by the underlying MSAL library, but that does not appear to be happening. Additionally, I'm expecting to avoid CORS errors on the front-end related to token expiration.
EDIT: While my main question still remains, I believe I found the resolution for the secondary issue: the CORS issue which is actually triggered via an AJAX call to the API. This article outlines that Microsoft.Identity.Web v1.2.0+ now handles this scenario. I now have a vague idea on how to handle it, but still need to attempt the implementation.
I found a reference here explaining that these session token caches have a scoped lifetime and should not be used when TokenAcquisition is used as a singleton, which I believe is the case with the use of the Microsoft Graph API ("AddMicrosoftGraph").
I switched the session token cache to a distributed SQL token cache. However, I do not believe any of this was actually the root issue.
I've identified an issue causing my server (clustered behind a LB without sticky sessions) encryption keys to not be correctly stored/shared in a distributed store. What was happening is any idle timeout in ISS would reset them, causing the auth cookie to be unusable. Additionally, any time the app would hit a different web server behind the LB, the existing auth cookie to be unusable by the new server (because they were using separate keys). So in both scenarios the application would redirect the user for authentication.
The fix for this was simply implementing a distributed key store as described here. The provided stores did not work for me, due to restrictions put in place by my client, so I just implemented a custom IXmlRepository and registered it:
services.Configure<KeyManagementOptions>(options => options.XmlRepository = new CustomXmlRepository());
So at the end of the day I had the following issues:
The auth cookie was becoming invalidated due to changing/lost keys as described above: Resolved by adding a distributed key store
The Microsoft GraphServiceClient was unable to obtain access tokens/refresh tokens (resulting in MSAL errors), due to a lack of a distributed token store as well as due to changing/lost keys (when I was storing tokens in the cookies): Resolved by adding a distributed token store (described here)

Get a token for the current logged in user, using MSAL.NET

I'm trying to get a token for the current logged in user, using MSAL.NET
I'm getting a reference to PublicClientApplication, using the following API:
_clientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.WithBroker()
.WithDefaultRedirectUri()
.Build();
Then, later in the code, I'm trying to call AcquireTokenSilent with the following code:
var firstAccount = Microsoft.Identity.Client.PublicClientApplication.OperatingSystemAccount;
authResult = await _clientApp.AcquireTokenSilent(scopes, firstAccount)
The call to AcquireTokenSilent thow an exception, with the following details:
Microsoft.Identity.Client.MsalServiceException
HResult=0x80131500
Message=WAM Error Microsoft.Identity.Client.Platforms.Features.WamBroker.AadPlugin
Error Code: WAM_aad_provider_error_3399614466
Error Message:
WAM Error Message: V2Error: invalid_request AADSTS90002: Tenant '75950cd6-4468-4918-b750-966c6b4fa3d1' not found. Check to make sure you have the correct tenant ID and are signing into the correct cloud. Check with your subscription administrator, this may happen if there are no active subscriptions for the tenant.
Interestingly, the SID '75950cd6-4468-4918-b750-966c6b4fa3d1' is NOT my tenant ID, and actually, I do not have this string anywhere in my code.
In the application registration, I set up the redirect URI to:
ms-appx-web://microsoft.aad.brokerplugin/<my application SID>. The application is registered for domain only, and the my device is domain joined.
What am I doing wrong?
Update
Answer a comment from #Sridevi.
I'm following this demo almost to the letter: https://learn.microsoft.com/en-us/azure/active-directory/develop/tutorial-v2-windows-desktop
With the following changes:
As a redirect URI, I wrote: ms-appx-web://microsoft.aad.brokerplugin/<client SID>
When building the MSAL application, I added .WithBroker() as mentioned above
For firstAccount, I do not take the first account from the list of acounts as in the tutorial (it is empty the first time), but I'm using the enum OperatingSystemAccount, mentioned above as well.
I would appreciate a sample or tutorial for getting this token otherwise.

How to obtain an Azure B2C bearer token for a non-interactive/daemon application and get it validated in an Azure HTTP-triggered function

There is a C# application under development that is supposed to be a part of a bigger backend application to process some data. This application is supposed to obtain a token from Azure AD B2C and send it to an HTTP-triggered function where it is supposed to be validated by the following code:
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
$"{_authenticationSettings.Authority}/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
var config = await configManager.GetConfigurationAsync();
_validationParameters = new TokenValidationParameters
{
IssuerSigningKeys = config.SigningKeys,
ValidateAudience = true,
// Audience MUST be the app ID aka clientId
ValidAudience = _authenticationSettings.ClientId,
ValidateIssuer = true,
ValidIssuer = config.Issuer,
ValidateLifetime = true
};
var tokenHandler = new JwtSecurityTokenHandler();
var result = tokenHandler.ValidateToken(authHeader.Parameter, _validationParameters, out var jwtToken);
First, we thought that obtaining an access token from Microsoft Graph API using MSAL would help us but the C# code above threw an invalid signature exception which we discovered makes sense due to this GitHub post. Apparently, we need to obtain an id_token instead in the application and send it to the HTTP-triggered function for validation by the code snippet above.
The application cannot obtain the id_token because it's not supposed to launch Azure AD B2C's login UI to have a user sign-in and redirect it through a URL. What is the solution to this problem so that the application would obtain a token without a UI and send that to the http-triggered function for validation?
Obtaining a token for the AAD B2C tenant without UI is possible in two ways and you should probably pick one depending on what exactly you want to achieve:
user token - by using Resource Owner Password Credentials flow - https://learn.microsoft.com/en-us/azure/active-directory-b2c/add-ropc-policy. This flow is deprecated though and mentioned usually in legacy application context
server-side application token - by using Client Cretendial flow - this on the other hand requires using requests specific for AAD but with AAD B2C tenant - https://learn.microsoft.com/en-us/azure/active-directory-b2c/application-types#daemonsserver-side-applications
I'm also not quite sure why should you use id_token for that. If the application needs to authorize the request to the function with the token then it should be an access token regardless of how the token is retrieved (interactive UI or not).

Aquire Token with ADAL.Net throws Unknown User Type for Managed AD Account

I am trying to call a web (api) service using a OAuth2 token based on a AAD managed user account logged in to an AAD joined machine using ADAL.Net - specifically using this example:
https://github.com/AzureAD/azure-activedirectory-library-for-dotnet/wiki/AcquireTokenSilentAsync-using-Integrated-authentication-on-Windows-(Kerberos)
However I keep getting the exception: Unknown User Type
In my setup I have logged onto a machine inside an AAD private network with a synced AAD user account. I then run the example code using WindowsAuthentication.
After some debugging I can narrow the exception to be thrown from this method in ADAL.Net
protected internal /* internal for test only */ override async Task PreTokenRequestAsync()
{
await base.PreTokenRequestAsync().ConfigureAwait(false);
if (!SupportADFS)
{
var userRealmResponse = await _commonNonInteractiveHandler.QueryUserRealmDataAsync(Authenticator.UserRealmUriPrefix)
.ConfigureAwait(false);
if (string.Equals(userRealmResponse.AccountType, "federated", StringComparison.OrdinalIgnoreCase))
{
WsTrustResponse wsTrustResponse = await _commonNonInteractiveHandler.PerformWsTrustMexExchangeAsync(
userRealmResponse.FederationMetadataUrl,
userRealmResponse.CloudAudienceUrn,
UserAuthType.IntegratedAuth).ConfigureAwait(false);
// We assume that if the response token type is not SAML 1.1, it is SAML 2
_userAssertion = new UserAssertion(wsTrustResponse.Token, (wsTrustResponse.TokenType == WsTrustResponse.Saml1Assertion) ? OAuthGrantType.Saml11Bearer : OAuthGrantType.Saml20Bearer);
}
else
{
throw new AdalException(AdalError.UnknownUserType);
}
}
}
Since everything in my setup is managed with AAD I do not see why the user account type needs to be "federated" in order for a token to be retrieved.
So I suspect that I need to get my token in another way!?
Any help will be appreciated ;)
After investigating we found that the above code (ADAL.Net) can only be used with a federated setup.
Federation means that you have an on premise network - which holds your windows user accounts - connected to an Azure AD network - which then "federates" these accounts to Azure AD. However it would be good to have a member of the ADAL team to comment on this.
Obtaining a token for a windows user account in a pure Azure AD (Managed setup) can supposedly be done using this code:
var connString = $"RunAs=App;AppId={appId};TenantId={tenantId};AppKey={appKey};";
var azureServiceTokenProvider = new AzureServiceTokenProvider(connString2);
var accessToken = azureServiceTokenProvider.GetAccessTokenAsync(service, tenantId).Result;
which is descripbed here: https://learn.microsoft.com/en-us/azure/key-vault/service-to-service-authentication#running-the-application-using-managed-identity
Again its not that well documented so any clarity from microsoft would be good.

Azure Active Directory v2.0 Daemons and Server Side Apps Support

Trying to get clarity as to if the current v2.0 endpoint supports the Daemons and server-side apps flow.
This article talks about the flows: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-v2-flows
It states:
This article describes the types of apps that you can build by using Azure AD v2.0, regardless of your preferred language or platform. The information in this article is designed to help you understand high-level scenarios before you start working with the code.
Further it states:
Currently, the types of apps in this section are not supported by the v2.0 endpoint, but they are on the roadmap for future development. For additional limitations and restrictions for the v2.0 endpoint
In the end I'm trying to build an app that connects to the Graph API that on a schedule connects to the API with "credentials" that allow it to access the API on behalf of a user that has allowed it to.
In my test harness I can get a token using:
var pca = new PublicClientApplication(connector.AzureClientId)
{
RedirectUri = redirectUrl
};
var result = await pca.AcquireTokenAsync(new[] {"Directory.Read.All"},
(Microsoft.Identity.Client.User) null, UiOptions.ForceLogin, string.Empty);
In the same harness I cannot get a token using:
var cca = new ConfidentialClientApplication(
connector.AzureClientId,
redirectUrl,
new ClientCredential(connector.AzureClientSecretKey),
null) {PlatformParameters = new PlatformParameters()};
var result = await cca.AcquireTokenForClient(new[] { "Directory.Read.All" }, string.Empty);
This will result in:
Exception thrown: 'Microsoft.Identity.Client.MsalServiceException' in mscorlib.dll
Additional information: AADSTS70011: The provided value for the input
parameter 'scope' is not valid. The scope Directory.Read.All is not
valid.
Trace ID: dcba6878-5908-44a0-95f3-c51b0b4f1a00
Correlation ID: 1612e41a-a283-4557-b462-09653d7e4c21
Timestamp: 2017-04-10 20:53:05Z
The MSAL package, Microsoft.Identity.Client (1.0.304142221-alpha), has not been updated since April 16, 2016. Is that even the package I should be using?
When using client credentials flow with Azure AD V2.0 , the value passed for the scope parameter in this request should be the resource identifier (Application ID URI) of the resource you want, affixed with the .default suffix. For the Microsoft Graph example, the value is https://graph.microsoft.com/.default.
Please click here for more details . And here is a tutorial for using client credentials flow with Azure AD V2.0 endpoint.

Resources