Daemon Apps and Scopes - c#

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.

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.

Calling a Web API secured by Azure B2C using .NET 5

I'm trying to call a Web API secured by Azure B2C. I'm using .NET 5. I'm also using Azure B2C to secure my WebApp.
In my WebApp startup.cs I have:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAdB2C"))
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamWebApi("API", Configuration.GetSection("AzureB2API"))
.AddInMemoryTokenCaches();
I'm using ITokenAcquisition to get the access token. I tried IDownstreamWebApi, but that didn't work.
string accessToken = await _tokenAcquisition.GetAccessTokenForUserAsync(Scopes);
The problem I'm having is that, the version of the access token I'm receiving is version 1. However, the Web API is expecting version 2.
I tried to change the version to 1 in the manifest of the Azure B2C WebAPI (accessTokenAcceptedVersion) but it will not accept the change.
Any advice will be greatly appreciated.
The startup.cs of the Web API is:
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
Please check if below can be a work around.
AddInMemoryTokenCaches adds an in memory token cache provider, which will cache the Access Tokens acquired for the downstream Web API.
services.AddMicrosoftIdentityWebAppAuthentication(Configuration,
"AzureAdB2C")
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["TodoList:TodoListScope"] })
.AddInMemoryTokenCaches();
services.AddInMemoryTokenCaches();
So,you may clear the cache after changing the accepted version so that app doesn’t take the previous version which is cached and used till it got expired.
reference1
As you mentioned the Web API is expecting version 2, I think you may have to change the accessTokenAcceptedVersion to 2 in the manifest of the Azure B2C WebAPP that calls the web api in the portal.
"accessTokenAcceptedVersion": 2,
Note
If the value of Supported account types is Accounts in any
organizational directory and personal Microsoft accounts (e.g.
Skype, Xbox, Outlook.com), the accepted token version must be v2.0.
Otherwise, the accepted token version can be v1.0
If the value is 2, the web API accepts v2.0 tokens.
If the value is null, the web API accepts v1.0 tokens.
Other points:
Also do check for the scope and permissions Protected web API app registration
/ The ASP.NET core templates are currently using Azure AD v1.0, and
compute // the authority (as {Instance}/{TenantID}). We want to use
the Microsoft Identity Platform v2.0 endpoint
options.Authority =
options.Authority + "/v2.0/";
Also see this

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

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.

Should Managed Service Identities be used for Azure App Service access from Console App

I have a console app that is running inside our enterprise that needs to access as App Service Web API. What is the best way to handle authentication. I tried registering the App with AD, but it still seems like it cant't see the App Service. I tried the following code, but I am not sure this is even the right API to use.
var App = ConfidentialClientApplicationBuilder.Create(CoreConstants.Auth_ClientId)
.WithAuthority(CoreConstants.Auth_Authority)
.WithClientSecret("xxxxxxxxxxxxxx")
.Build();
var token = App.AcquireTokenForClient(scopes).ExecuteAsync();
token.Wait();
This fails saying the scope is not defined. It looks like it is in Azure.
First of all, you need to create role assignments for your App identity. And then you can get the access tokens from the identity. The code will like this:
using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net");
// OR
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
Do not forget to add references to the Microsoft.Azure.Services.AppAuthentication and any other necessary NuGet packages to your application. For more details, see Obtaining tokens for Azure resources with App MSI.

Categories