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
Related
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.
According to this official Microsoft document (and the documents referenced there) URIs using login.microsoftonline.com/myTenant/.. must be replaced by myTenant.b2clogin.com/mytenantId/..
For the interactive login with MSAL to get an ID token this works fine for me. But I can not figure out which URI to use with IConfidentialClientApplication and using a secret. I'm using MSAL 4.18 in a C# Windows application.
The following code works fine using login.microsoftonline.com.
IConfidentialClientApplication app = ConfidentialClientApplicationBuilder
.Create(clientId)
// how must this URL look like when using b2clogin.com
.WithAuthority("https://login.microsoftonline.com/*MyTenant*.onmicrosoft.com/v2.0")
.WithClientSecret(secret)
.Build();
AuthenticationResult authenticationResult = await app
.AcquireTokenForClient(scopeArray)
.ExecuteAsync();
Whatever b2clogin.com URI I use (e.g. https://*myTenant*.b2clogin.com/*myTenantId*/v2.0) I get back an AADSTS50049: Unknown or invalid instance. response.
What am I missing? Can I continue to use login.microsoftonline.com for this case?
(This answer comes with a bit of twists, so please read till end :))
You should not generally continue to use login.microsoftonline.com for B2C, it will be retired on December 4, 2020.
Authority should be in format https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-ID}/{policyname} or https://{your-tenant-name}.b2clogin.com/tfp/{your-tenant-name}.onmicrosoft.com/{policyname}
Also, you should be using .WithB2CAuthority, not .WithAuthority for B2C in code for client setup (but there is a catch here for your case since it's Client Credential Grant which is in the later part of the answer).
Example code (though the example uses public client, in your case it's confidential client, just referring it for authority reference).
For details, refer this.
But since you are using Client Credential Grant Flow using client id and secret, it's not directly supported in B2C as documented here.
Although the OAuth 2.0 client credentials grant flow is not currently
directly supported by the Azure AD B2C authentication service, you can
set up client credential flow using Azure AD and the Microsoft
identity platform /token endpoint for an application in your Azure AD
B2C tenant. An Azure AD B2C tenant shares some functionality with
Azure AD enterprise tenants.
In summary, you can not use B2C directly for Client Credential Grant and workaround requires to use regular common enterprise AAD backend of B2C. So b2clogin.com part would not be applicable for this workaround. That mean only for this particular Client Credential Flow case, you should be continuing with login.microsoftonline.com since you are not actually using B2C in true sense for this.
I have a desktop app which uses ADAL for authentication, this app make requests to an API on the API Management azure service. After migrating the code to use MSAL, the API Management returns 401 saying that my token is invalid. The only difference that I see spying the requests is that ADAL makes a request to this endpoint /tenantID/oauth2/token and MSAL /tenantID/oauth2/v2.0/token.
In my API Management I have this policy:
<validate-jwt header-name="Authorization" failed-validation-httpcode="401" failed-validation-error-message="Unauthorized. Access token is missing or invalid. AAD" require-expiration-time="false">
<openid-config url="https://login.microsoftonline.com/tenantID/.well-known/openid-configuration" />
</validate-jwt>
I tried to change the well known url to v2.0 endpoint but get the same error. How can I validate the token using MSAL?
From the Note in the doc, when changing the well known url to v2.0, you may need to use common instead of tenantID.
<openid-config url="https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration" />
Also, make sure you have done the step 10 in this link correctly:
If you use v2 endpoints, use the scope you created for the backend-app
in the Default scope field. Also, make sure to set the value for
the accessTokenAcceptedVersion property to 2 in your
application manifest.
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.
I have a native client application which is obtaining an OAuth2 token for Active Directory authorization. It will then use that token to communicate with a secure Web API server where certain areas of the API are secured using the [Authorize] attribute. The server is also registered with Azure AD and can properly authorize requests through AD.
When I try to get the token, I get the following exception on Line 2 of the code below:
Additional information: invalid_request: AADSTS90027: The client '<Client GUID>' and resource 'https://abccompany.com/MyApplication.Server' identify the same application.
Here is my code which I am running in the native client (just on a button press as a test, for now). Obviously the GUID and company names have been obfuscated.
AuthenticationContext ac = new AuthenticationContext("https://login.windows.net/abccompany.com");
AuthenticationResult ar = ac.AcquireToken("https://abccompany.com/MyApplication.Server", "<Client GUID>", new Uri("https://localhost:44300/secure"), PromptBehavior.Auto);
I made sure the redirect existed in Azure in the application configuration (otherwise there would have been a redirect error instead). What does the error mean?
You seem to be using the clientId of the WebAPI where you need to supply the clientId of the client app. Please register a separate 'Native client application' in Azure AD representing the client app.
The following topic explains the protocol flow and how to register WebAPIs in Azure AD such that users from multiple AD tenants can use that API: http://msdn.microsoft.com/en-us/library/azure/dn499820.aspx#BKMK_Native
The following samples should see you through:
Single tenant WebAPI: https://github.com/AzureADSamples/NativeClient-WindowsStore
Multi-tenant WebAPI: https://github.com/AzureADSamples/NativeClient-WebAPI-MultiTenant-WindowsStore
Hope this helps.
ps: Azure AD doesn't issue a token when the client and resource are the same application. In your case they should indeed be different and resource clientid (issued to a confidential client) should not be used as a public client - however for service to service scenarios, it can be argued that issuing tokens to self should be allowed - this is something we are looking into.