Authorization has been denied for this request - Desktop to ASP.NET Web API - c#

I have been banging my head against a wall for some time now.
Desktop WPF app calling a ASP.NET Web API.
I am using the [AUTHORIZE] annotation on the ASP.NET app. This is where the problems have started.
Using MSAL from the WPF app.
static App()
{
_clientApp = PublicClientApplicationBuilder.Create(ClientId)
.WithAuthority(AzureCloudInstance.AzurePublic, Tenant)
.WithDefaultRedirectUri()
.Build();
}
ClientId refers to the app registration of the desktop app in Azure.
string[] scopes = new string[] { "api://****-f56f-4cec-a771-dbdb5d43f047/access_as_user" };
var accounts = await App.PublicClientApp.GetAccountsAsync();
AuthenticationResult authResult;
try
{
authResult = await App.PublicClientApp
.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (Exception)
{
authResult = await App.PublicClientApp
.AcquireTokenInteractive(scopes)
.ExecuteAsync();
}
App registration for the web api is also there. I have set up the scope via 'Expose an API' and given delegated permission to the desktop app to call into the web api.
When I call in I get
StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.StreamContent, Headers
I can call a non AUTHORIZE endpoint no problem, so the api is working fine.
I have endlessly been through the MSAL documentation.
Things I am unsure about.
AppRoles in the manifest.
Do they need to be authorised anywhere apart from adding to the manifest?
Do I leave App Service (Web api) as 'Anonymous access is enabled on the App Service app. Users will not be prompted for login.' Is MSAL taking care of that.
I am assuming you can either use MSAL code or secure the api via AD (Authenication/Authorization)
I have dug myself in a hole and can see out right now, so excuse me a little.
Thanks

please take a look at: https://learn.microsoft.com/en-us/azure/app-service/configure-authentication-provider-aad#-configure-with-advanced-settings
at the top of that document there is a note saying:
Note
The express settings flow sets up an AAD V1 application registration. If you wish to use Azure Active Directory v2.0 (including MSAL), please follow the advanced configuration instructions.
basically means that you can't really use msal with the express setup. but can with advanced.
then the section under it describes what you need to do for a desktop app in terms of adding api permissions to your app service.
Hopefully this puts you a bit on the right track, if not please comment further and i will try to help as much as possible.

Related

Trouble with On-Behalf-Of flow with standalone Blazor WASM, AAD, .NET Core 6 Web API calling MS Graph

I have a standalone Blazor WASM site (client), a separate .NET 6 web API (server) with protected endpoints and I'm trying to call MS Graph from the API.
I've read just about every article I could find on the configuration required to make this work and I'm stuck with the incremental consent failing. I get the following error when trying to access a server API which uses MS Graph:
Error acquiring a token for a downstream web API - MsalUiRequiredException message is: AADSTS65001: The user or administrator has not consented to use the application with ID '[redacted]' named '[redacted]'. Send an interactive authorization request for this user and resource.
Configuration...
Created AAD app for Web API (server), added secret for Graph configuration, set the app URI and created access_as_user scope under "Expose an API" in AAD.
Added the client ID (from the following step) to the knownClientApplications section in the manifest for the server app registration in AAD.
For API Permissions I added Graph scopes User.Read, User.Read.All, and Group.Read.All and provided admin consent in the AAD UI.
Configured appsettings.json in the API to add the Graph API BaseUrl and above scopes from step 2 along with the correct AzureAD domain, TenantId, ClientId, and ClientSecret values for MSAL to function.
Configured MSAL on the server:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraph(builder.Configuration.GetSection("MicrosoftGraph"))
.AddInMemoryTokenCaches();
Created AAD app for Blazor WASM, used SPA auth w/redirect to https://localhost:7014/authentication/login-callback and set the API permissions to api://[redacted]/access_as_user only.
Created custom authorization message handler according to this article.
public CustomAuthorizationMessageHandler(IAccessTokenProvider provider, NavigationManager navigation) : base(provider, navigation)
{
ConfigureHandler(
authorizedUrls: new[]
{
"https://localhost:7069"
},
scopes: new[]
{
"api://[redacted]/.default"
});
}
Configured MSAL on the client:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("api://[redacted]/.default");
options.ProviderOptions.LoginMode = "redirect";
}
Set up named HTTP client on the Blazor client with custom message handler:
var baseAddress = builder.Configuration["PublicApiUrl"];
builder.Services.AddHttpClient("PublicApi", client =>
{
client.BaseAddress = new Uri(baseAddress);
}).AddHttpMessageHandler<CustomAuthorizationMessageHandler>();
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("PublicApi"));
builder.Services.AddScoped<CustomAuthorizationMessageHandler>();
What works...
I can authenticate as an AAD user to the Blazor client.
I can access protected endpoints (using policy-based authorization) hosted on the server which don't have a dependency on MS Graph.
Questions...
Following this article's guidance about incremental consent, specifically the "Static permissions" section, I would assume granting admin consent for Graph on the server's app registration would suffice?
All of the documentation showing Blazor WASM with a protected API calling a protected API (Graph) assume the Blazor client is also hosted by the API server. Is it even possible to use on-behalf-of flow in my case? If it was hosted I could see the API calling the Blazor navigation subsystem to perform an incremental consent redirect but when they're separated, I can only imagine the static permissions is the way to go.
Is it necessary to set the DefaultAccessTokenScopes in the client?
The issue here is use of the AddMicrosoftGraph method when the API application is being built.
The GraphServiceClient created by AddMicrosoftGraph will have default access to delegated permissions which are assigned to users as opposed to application permissions which are assigned to applications. This is why the MsalUiRequiredException is being thrown which is usually resolved by prompting the user to login.
You can read more about delegated vs application permissions here.
What you can do instead is use the AddMicrosoftGraphAppOnly method to create a GraphServiceClient that will use credentials specific to your API to retrieve the relevant data needed from the Microsoft Graph API.
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration)
.EnableTokenAcquisitionToCallDownstreamApi()
.AddMicrosoftGraphAppOnly(
authenticationProvider => new GraphServiceClient(authenticationProvider))
.AddInMemoryTokenCaches();
So long as you have the relevant settings and secrets provided in the AzureAd section of your appsettings.json file the GraphServiceClient injected into your application should now be able to access the data you need.
You can read more about app configuration with the AzureAd settings in your appsettings.json file here.

What type of ApplicationBuilder should I use in MSAL.NET? I'm using ASP.NET Core Web Api

What I'm trying to achieve is to popup a login browser from MSAL.NET, enter username and password, and used the access token to access Microsoft Graph.
Right now I used PublicClientApplicationBuilder to execute AcquireTokenInteractive to popup the login by MSAL.
I'm using ASP.NET Core Web Api.
The problem is, I'm having issue using PublicClientApplicationBuilder when deployed to IIS. It just stucks and always in Pending state.
Below is my sample code that always in Pending state when deployed to IIS:
var app = PublicClientApplicationBuilder.Create(clientId)
.WithDefaultRedirectUri()
.WithTenantId(tenantId)
.Build();
var result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
And now I read an article from here: https://learn.microsoft.com/en-us/answers/questions/91715/msal-acquiretokeninteractive-code-hangs-infinte-lo.html
To use the ConfidentialClientApplicationBuilder. Now the problem is there is no execute in ConfidentialClientApplicationBuilder to popup login browser from MSAL just like the AcquireTokenInteractive.
There are only AcquireTokenSilent, AcquireTokenByAuthorizationCode, AcquireTokenOnBehalfOf, and AcquireTokenForClient. But all of these don't seem to popup a login browser from MSAL.NET
Below is my sample code:
var confidentialClientApplication = ConfidentialClientApplicationBuilder
.Create(clientId)
.WithTenantId(tenantId)
.WithClientSecret(clientSecret)
.Build();
var result = await confidentialClientApplication.AcquireTokenForClient(scopes).ExecuteAsync();
How do I manage to popup a login browser from MSAL by using ConfidentialClientApplicationBuilder?
You should first know about MSAL. You have an asp.net core web app, and you wanna your user to sign in in a popup window and generate access token to call Ms graph api, so you need to refer to this document to integrate azure ad into your web application.
What you mentioned in the question about having issue when deployed to IIS comes from using error method. When you test in your local side with those code, your local computer becomes the sever, it is supposed to work, but if you published the app to IIS, that means the users are hit your app in the client side but the pop up action will appear in the sever side. That's why it always pending.
To sum up here, if you need your users signed in and generate access token with delegate api permission, you should follow the document I post above to realize the feature. But if you can use application permission to generate access token as well, you can then go to use graph SDK with client credential flow to realize it.

Retrieve 2 Access Tokens on Interactive Authentication in Azure AD

Development Information: Azure Registered Mobile Application, Azure hosted API, Visual Studio 2019, mobile application is built in Xamarin Forms targeting .Net-5 and API is built in Asp.Net targeting .Net-5.
Desired Workflow: User does Authorization code flow authentication interactive if AcquireTokenSilently fails, utilizes MS Authenticator. Once authenticated, the user gets an access token for Graph and for the custom hosted API.
What is working: I can do the MFA authentication piece using the MSAL library and an access token is received in the mobile application, but the token is only good for making calls to Graph. I need to be able to call the protected resource (hosted API) as well. My thoughts originally was that the JWT access token would work with the hosted API as well as Graph, but after reading more, I have read that you have to acquire two separate tokens as to prevent issues where two resources may have the same scope(s).
The problem I am facing is that I thought upon Authenticating there would be an easy way to get another resources access token without having to authenticate a second time. Isn't that the point of the OpenID authentication?? My last thought was to do an implicit authentication from the mobile app to the protected API using a client id, client secret, but I don't want to store a client secret in the mobile app for fear it may expose something sensitive to a user. I tried following the documentation of Microsoft and set up specific scopes for the hosted API and registered it in the Azure portal, but this didn't seem to have any affect on the authentication piece. I still have to authenticate against the API which isn't the desired result.
Is it possible to authenticate once for Graph and then knowing that the user authenticated correctly as they now have a valid token for Graph then somehow call this protected API without having to force them to do authentication again for the protected API and if so, how to do this without exposing any sensitive information (client secret) in the mobile application?
CODE
// Microsoft Authentication client for native/mobile apps
public static IPublicClientApplication PCA;
//Setting up the PublicClientApplicationBuilder
//OAuthSettings is a static class with required values (app id, tenant id, etc)
var builder = PublicClientApplicationBuilder
.Create(OAuthSettings.ApplicationId)
.WithTenantId(OAuthSettings.TenantId)
.WithBroker()
.WithRedirectUri(OAuthSettings.RedirectUri);
PCA = builder.Build();
//Scopes being used in initial authentication
//I tried adding to this with a custom scope, I added on the
//protected api and it caused an exception, so I didn't think
//I could use those together with the scopes for Graph
//Custom scope for the protected API is as follows:
//XXX.User.Common (where XXX is our company name)
public const string Scopes = "User.Read MailboxSettings.Read Calendars.ReadWrite";
try
{
var accounts = await PCA.GetAccountsAsync();
var silentAuthResult = await PCA
.AcquireTokenSilent(Scopes.Split(' '), accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException msalEx)
{
var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();
// Prompt the user to sign-in
var interactiveRequest = PCA.AcquireTokenInteractive(Scopes);
AuthUIParent = windowLocatorService?.GetCurrentParentWindow();
if (AuthUIParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(AuthUIParent);
}
var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
var accounts = await PCA.GetAccountsAsync();
//at this point, I have a valid Graph token, but I need to
//get a valid token for the protected API,
//unsure of how to do this last piece
}
Much of this answer is thanks to Gaurav Mantri but he was too modest to accept credit, and he asked me to post an answer.
First thing I needed to change was the flow of the authentication.
Authenticate the user against the protected API, then get the Graph token, as the Graph authentication works well with the AcquireTokenSilent(IEnumerable<string> scopes, IAccount account) method of MSAL library. I was doing this backwards by authenticating against Graph then trying to get the protected API token.
So, the code for authenticating then looks like the following:
public async Task SignIn()
{
try
{
//check for any accounts which are authenticated
var accounts = await PCA.GetAccountsAsync();
//try to Authenticate against the protected API silently
var silentAuthResult = await PCA
.AcquireTokenSilent(new string[] { "api://{your api client id}/.default" }, accounts.FirstOrDefault())
.ExecuteAsync();
}
catch (MsalUiRequiredException msalEx)
{
// This exception is thrown when an interactive sign-in is required.
var windowLocatorService = DependencyService.Get<IParentWindowLocatorService>();
// Prompt the user to sign-in
var interactiveRequest = PCA.AcquireTokenInteractive(new string[] { "api://{your api client id}/.default" });
AuthUIParent = windowLocatorService?.GetCurrentParentWindow();
if (AuthUIParent != null)
{
interactiveRequest = interactiveRequest
.WithParentActivityOrWindow(AuthUIParent);
}
var interactiveAuthResult = await interactiveRequest.ExecuteAsync();
var accounts = await PCA.GetAccountsAsync();
//Now we can get the Graph token silently
//We now have valid tokens for both Graph and our protected API
var graphtokenresult = await PCA.AcquireTokenSilent(Scopes, accounts.First()).ExecuteAsync();
}
catch (Exception ex)
{
}
}
In the Azure portal there were a few things I needed to ensure were set in the configurations.
Make sure you set some custom scopes for the protected API - quickstart-configure-app-expose-web-apis
(This one isn't documented in the MSDN documentation)
On the same page you can add scopes for the protected API, there is a section called Add a client application, you would think by clicking add and then selecting an application which you want to grant permission to access your API would suffice, but it is not.
You also need to go into the protected API's Manifest and add the client id of the application you want to grant access manually, as clicking the add button and selecting the client application does not modify the manifest. So, open the manifest of your protected API and add the client application id to the section of the manifect labeled knownClientApplications:
Once all of this has been done, you can now receive an access token which will authorize against the protected API as well as a Graph token for getting user information. I hope this is helpful and thanks again to Gaurav Mantri. If anyone has more questions about this, please contact me and I'll do my best to pass on what I have learned.

Daemon Apps and Scopes

So, I am trying to do see if I can support a scenario where a C# daemon app can access a custom Web API protected with MSAL.NET and OAuth2 scopes. As of now, I see no way of doing this.
The versions of the libraries and toolsets are:
.NET Core 2.2
Microsoft.Identity.Client 4.1.0
The client is
var app = ConfidentialClientApplicationBuilder.Create("<client app id>")
.WithClientSecret("<client_secret>")
.WithAuthority("https://login.microsoftonline.com/<tenant_id_that_hosts_the_web_api>")
.Build();
and then to acquire token
await app.AcquireTokenForClient(new string[] { "api://<app_id_of_the_web_api>/.default" });
At this point, I do get the token back with which I call my custom Web API end point protected using MSAL and an Azure App with the above mentioned App ID. This doesn't work since I have a policy based authorization on the end point, expecting a specific custom scope defined in the Azure AD app.
The question is, how do I configure the client and Azure AD so I will get the specific scopes passed in as claims for the Web API?
You need to register two applications, one for daemon app(client app), one for web api(backend app).
Click the web api app->Expose an API.
Click the daemon app->API permissions->Add a permission->My APIs->choose web api app->select the permissions.
Then the client
var app = ConfidentialClientApplicationBuilder.Create("<client app id>")
.WithClientSecret("<client app client_secret>")
.WithAuthority("https://login.microsoftonline.com/<tenant_id>")
.Build();
The scope:
await app.AcquireTokenForClient(new string[] { "api://<app_id_of_the_web_api>/read" });
Refer to this sample. You can think of your web api as Microsoft Graph API.
First of all thanks much, Caiyi, for the pointers. That got me thinking the right way about the approach. Unfortunately, I am forced to use the api:///.default to get an access token. No amount of cajoling seems to work when using a "regular" scope. I configured the apps as was suggested above, but instead of:
await app.AcquireTokenForClient(new string[] { "api://<app_id_of_the_web_api>/read" });
I had to use:
await app.AcquireTokenForClient(new string[] { "api://<app_id_of_the_web_api>/.default" });
In the configuration for the API app, I had to define an "appRole" in the manifest that identifies the role that I was going to assign the Daemon app and then in the Web API, I changed my policy code to check for the scopes OR the app role - which worked.

Authenticating HttpClient calls from .NET Core on MacOS

My question today is:
How to configure HttpClient so that it can authenticate the call without bothering the user on MacOS?
(.NET Core 2.2 console app running as Launch Agent on MacOS, calling a Web API on IIS with NTLM and Kerberos enabled, over our company's internal network)
Long story:
I have a .NET Core app that uses the following method to call a web api:
var handler = new HttpClientHandler()
{
UseDefaultCredentials = true
};
var client = new HttpClient(handler)
{
BaseAddress = new Uri("https://MyWebAPI.MyCompanyName.com/")
};
string result = client.GetAsync("MyEndpointSubURL")
.Result.Content.ReadAsStringAsync().Result;
When I run this on my Windows machine, the app easily connects and gets the result.
However, when I run this on a Mac, I get an exception:
Interop+NetSecurityNative+GssApiException - GSSAPI operation failed with error
The provided name was not a mechanism name. (unknown mech-code 0 for mech unknown).
at Microsoft.Win32.SafeHandles.SafeGssNameHandle.CreatePrincipal(String name)
Any ideas what I need to change to make it work?
We desperately want to avoid bothering the user with prompts (it's meant to be a background syncing service).
Recall, it's a .NET Core 2.2 console app running as Launch Agent on MacOS. The Web API it's calling is an Asp.NET Web API hosted with IIS with NTLM and Kerberos enabled and I only need to get past IIS (web API does not use any authentication/authorization mechanisms by itself). The API is exposed only over our company's internal network, so the user is already logged in to the network.
Try running kinit <username>#<DOMAIN> from the terminal and then running your program again. You may need to configure your krb5.conf file to properly point to the domain controller.
We have "default credentials" working in our system on Mac w/ .NET Core 2.1+ using the same code you show there. Configuring Kerberos through kinit and the conf file is the biggest challenge.
Based on what I can tell, .NET doesn't use the cache produced from running kinit, but this is what configures the principal to be used. .NET's interaction with Kerberos is poorly documented. See https://github.com/dotnet/corefx/issues/30203#issuecomment-395592407
I had a very hard time getting this to work on macOS with .NET Core 2.2.
I followed the online documentation about setting up your krb5.conf, running kinit and klist to make sure I had a valid kerberos ticket.
After all that, kerberos was working with Azure Data Studio, so I knew my setup was okay, but I could not get HttpClient with UseDefaultCredentials = true working. It always failed with the same error:
System.ComponentModel.Win32Exception: GSSAPI operation failed with error - An unsupported mechanism was requested (unknown mech-code 0 for mech unknown).
It did however work on a coworker's machine.
After a lot of digging, we discovered my coworker had .NET Core 2.2.7 installed while I only had 2.2.1. Upgrading my workstation to .NET Core 2.2.8 resolved my issue. Also rolling back our app to use 2.1.13 worked as well.
I hope this helps someone else in the future.
Try this:
With basic auth example.
var url = "https://MyWebAPI.MyCompanyName.com/";
using (var httpClient = new HttpClient())
{
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Basic", "Base64Credetials");
using (var response = await httpClient.GetAsync(url))
if (response.IsSuccessStatusCode)
{
var strResponse = await response.Content.ReadAsStringAsync();
MyObject result = JsonConvert.DeserializeObject<MyObject>(strResponse);
if (result != null)
{
//Your code here
}
}
}
I don't think MacOS has a concept of "default authentication" in the same way Windows does. Kerberos and NTLM are both Windows concepts. I suspect on MacOS you will have to use a different authentication scheme (Basic or Bearer) and then retrieve the credentials from somewhere such as the Keychain. IIRC an app can be granted silent read access to the key chain.

Categories