ASP Net Core API downstream call to Graph API W/ AADB2C - azure-active-directory

I have an ASP Net Core API where I want to call Graph API. I configure the Authentication as such:
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, configSectionName: Constants.AzureAdB2C)
.EnableTokenAcquisitionToCallDownstreamApi(options => Configuration.Bind(Constants.AzureAdB2C, options))
.AddMicrosoftGraph(Configuration.GetSection("GraphAPI"))
.AddInMemoryTokenCaches();
My appsettings.json file has the following properties:
{
"AzureAdB2C": {
"Instance": "https://tenantName.b2clogin.com/",
"Domain": "tenantName.onmicrosoft.com",
"TenantId": "tenantId",
"ClientId": "appId",
"ClientSecret": "appSecret",
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn",
"ResetPasswordPolicyId": "B2C_1_PasswordReset"
},
"GraphAPI": {
"BaseUrl": "https://graph.microsoft.com/v1.0",
"Scopes": "User.Read Directory.ReadWrite.All"
}
}
My b2c app is granted permission to these Graph scopes.
I created an endpoint:
[HttpGet]
[Route("me")]
public Task<User> Me()
{
return this.graphServiceClient.Me.Request().GetAsync();
}
This is where I get this error:
ErrorCode: unsupported_grant_type
Microsoft.Identity.Client.MsalServiceException: AADB2C90086: The supplied grant_type [urn:ietf:params:oauth:grant-type:jwt-bearer] is not supported.
Why can't my API call GraphAPI? All samples that I saw used services.AddMicrosoftIdentityWebAppAuthentication.... Could that be the reason?

On-behalf-of flow in B2C is not supported: https://learn.microsoft.com/en-us/azure/active-directory-b2c/access-tokens.
Web API chains (On-Behalf-Of) is not supported by Azure AD B2C.
You need to acquire the token using application permissions as your application with client credentials flow.
There is some documentation on that: https://learn.microsoft.com/en-us/azure/active-directory-b2c/microsoft-graph-get-started?tabs=app-reg-ga#register-management-application.
The documentation creates a separate app registration for doing that though I think you can just add the app permissions to your existing registration.

Related

Why is SignUpSignInPolicyId required for an API?

This sample here shows providing a SignUpSignInPolicyId https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/tree/master/4-WebApp-your-API/4-2-B2C
See the appsettings.json for the service (Web API):
"AzureAdB2C": {
"Instance": "https://fabrikamb2c.b2clogin.com",
"ClientId": "90c0fe63-bcf2-44d5-8fb7-b8bbc0b29dc6",
"Domain": "fabrikamb2c.onmicrosoft.com",
"SignedOutCallbackPath": "/signout/B2C_1_susi_reset_v2",
"SignUpSignInPolicyId": "B2C_1_susi_reset_v2"
//"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc
},
I am confused what this does with an API, the API is protected by OAuth2 JWT it cannot control sign up or sign in at all. It just takes a token and validates it.
Why does it need to know anything at all about a sign up or sign in policy?
When I tried to run the application which calls api with policy ,I can login and open the redirect to app.
Appsettings.json
{
"AzureAdB2C": {
"Instance": "https://kavyab2c.b2clogin.com",
"ClientId": "xxxxxxx",
"Domain": "kavyab2c.onmicrosoft.com",
"SignedOutCallbackPath": "/signout/B2C_1_susi",
"SignUpSignInPolicyId": "b2c_1_susi",
"ResetPasswordPolicyId": "b2c_1_reset",
"EditProfilePolicyId": "b2c_1_edit_profile" // Optional profile editing policy
//"CallbackPath": "/signin/B2C_1_sign_up_in" // defaults to /signin-oidc
},
Even without policy it redirected to page.
But to obtain claims or reach the particular controller action or access an api, it requires user authentication if one needs to protect API.
Note:
SignUpSignInPolicyId should not be omitted when calling an Azure AD B2C protected API. If it is not specidfied, the API will not know which user flow to use and cant authenticate the user and gives error.
If custompolicy is used instead of userflow, the SignUpSignInPolicyId
parameter can be omitted, and the created custom policy can be used
to authenticate and authorize users directly by making use of their
username and password credentials.
Reference: Cloud authentication with Azure Active Directory B2C in ASP.NET Core | Microsoft Learn

Using Active Directory B2C to secure a Blazor WASM app and Web API

I have a scenario where I have a Blazor WASM (client only) app that is secured via AD B2C. As per the documentation I have read, I have registered an application in AD B2C (e.g. BlazorApp) and wired the Blazor app to this instance. This Blazor app makes API calls to a .NET Core Web API where the endpoints are secured (using the [Authorize] attributes). As per the documentation I have read, I have also registered the Web API in AD B2C (e.g. WebApi) and wired the API to connect to this instance.
The problem I have is that when I authenticate in the Blazor app, and then pass the access/id token through the API calls, the Web API can't authenticate that token (unauthorized error response). It works when I wire the Web API to connect to the BlazorApp registration (I'm guessing because that is where the token was issued from). But this seems to go against the recommendation of registering each app/api in AD as a separate registration.
How can I get the WebApi registration to recognise a token issued by BlazorApp? Am I going about this wrong and should I just wire the Web API to talk to the BlazorApp instance?
Additional Information:
Blazor WASM (client) - Program.cs
(sensitive information removed)
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
...
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(#"https://<tenant name>.onmicrosoft.com/blazorapp/app.read");
options.ProviderOptions.DefaultAccessTokenScopes.Add(#"https://<tenant name>.onmicrosoft.com/blazorapp/app.write");
});
...
await builder.Build().RunAsync();
}
Blazor WASM (client) - appsettings.json
(sensitive information removed)
{
"AzureAdB2C": {
"Authority": "https://<tenant name>.b2clogin.com/<tenant name>.onmicrosoft.com/B2C_1_SignUpSignIn",
"ClientId": "<BlazorApp Application ID>",
"ValidateAuthority": false
}
}
Web API (.NET Core 3.1) - StartUp.cs
(sensitive information removed)
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(AzureADB2CDefaults.BearerAuthenticationScheme)
.AddAzureADB2CBearer(options => Configuration.Bind("AzureAdB2C", options));
services.AddControllers();
...
}
Web API (.NET Core 3.1) - appsettings.json
(sensitive information removed)
NOTE: Authentication works when I replace the WebApi Application ID with the BlazorApp Application ID
{
"AzureAdB2C": {
"Instance": "https://<tenant name>.b2clogin.com/tfp/",
"ClientId": "<WebAPI Application ID>",
"Domain": "<tenant name>.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn"
},
...
}
Your project is not using OBO flow, OBO flow requires three applications, specifically one client-side and two api-side, see here.
Back to the problem, first of all your problem is misconfigured scope, as the api-side app needs to expose its own api, the API permission on your blazor app side gets access to the api-side, so the access to the scope is configured on the api-side, so we need to put https://<tenant name>.onmicrosoft.com/webapiapp/app.read on the scope, your blazor app is only used to configure access.
It works because you use blazor app to request the blazor app's own api, which is feasible, although a little strange, to request yourself.
So for your project, the correct request should be like this.
public static async Task Main(string[] args)
{
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("app");
...
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAdB2C", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add(#"https://<tenant name>.onmicrosoft.com/webapiapp/app.read");
options.ProviderOptions.DefaultAccessTokenScopes.Add(#"https://<tenant name>.onmicrosoft.com/webapiapp/app.write");
});
...
await builder.Build().RunAsync();
}
{
"AzureAdB2C": {
"Instance": "https://<tenant name>.b2clogin.com/tfp/",
"ClientId": "<Application ID>",
"Domain": "<tenant name>.onmicrosoft.com",
"SignUpSignInPolicyId": "B2C_1_SignUpSignIn"
},
...
}

Secure .Net Core 3 Web API with AAD Token

Due to some technical constraints, we are doing Username/Password AAD authentication when user login.
Users will input their username and password into our custom login page and our application calls IPublicClientApplication.AcquireTokenByUsernamePassword.
I'm planning to use the returned token to call another Web API application(also connecting to the same AAD). In the Web API application, I did the following:
Added the following code in startup services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme).AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
include the following settings in my appsettings.json file
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "<Application ID>",
"TenantId": "<Tenant ID>"
}
Secure my web api using [Authorize]
I then use Postman to construct a call to the Web API based on the returned token. I included Authorization: Bearer <JWT Token>. The Web API returns
Bearer error="invalid_token", error_description="The signature is invalid"
My questions are
Can Web API application validate the username/password acquired token?
If the token can be validated in Web API application, how can I do it since I'm getting the above error?
I test in my site and it work well, you could refer to the following steps:
1.Register Webapi app in azure ad.
2.Click Expose an API and Add a scope e.g. webread.
3.Click Manifest, change accessTokenAcceptedVersion to 2.0.
4.In visual studio webapi ConfigureServices:
services.AddAuthentication(AzureADDefaults.JwtBearerAuthenticationScheme).AddAzureADBearer(options => Configuration.Bind("AzureAd", options));
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme,
options =>
{
options.Authority += "/v2.0";
options.TokenValidationParameters.ValidAudiences = new[]
{
options.Audience,
$"api://{options.Audience}"
};
});
5.Register client app in azure ad.
6.Click Authentication, set Default client type as Yes.
7.Click Api Permission>Add a permission, select My APIs and choose the webapi your registered before.
8.In visual studio client app, set scope with webread:
string[] scopes = new string[] { "api://1890e822-xxxxxxxxxxxxxxxx/webread" };
Hope it helps you.
From the document you provided you are using MSAL to get access token using Resource Owner Flow in Azure AD V2.0 endpoint .
From document , when validating access token which issued from Azure AD V2.0 , you should add /v2.0 to Authority :
services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
{
// This is a Microsoft identity platform web API.
options.Authority += "/v2.0";
// The web API accepts as audiences both the Client ID (options.Audience) and api://{ClientID}.
options.TokenValidationParameters.ValidAudiences = new []
{
options.Audience,
$"api://{options.Audience}"
};
});

Azure B2C: Microsoft Graph API - InvalidAuthenticationToken

I'm trying to call /me on the Microsoft Graph API after logging into an Azure B2C Active Directory from iOS.
Using the sample application at: https://github.com/Azure-Samples/active-directory-b2c-ios-swift-native-msa I've replaced the constants such that sign-up and sign-in work. I've set the kGraphURI to https://graph.microsoft.com/v1.0/me. The code looks like this:
let kTenantName = "mytenant.onmicrosoft.com"
let kAuthorityHostName = "mytenant.b2clogin.com"
let kClientID = "xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
let kSignupOrSigninPolicy = "B2C_1A_signup_signin"
let kGraphURI = "https://graph.microsoft.com/v1.0/me"
let kScopes: [String] = ["https://mytenant.onmicrosoft.com/api/user_impersonation"]
The value for kScopes is set to the only API my application has available, which was created as part of the instructions for setting up user policies.
When I try to call https://graph.microsoft.com/v1.0/me I get back:
{ "error": {
"code": "InvalidAuthenticationToken",
"message": "Access token validation failure.",
"innerError": {
"request-id": "e923673f-25cb-44be-b3b9-94eda660d4f6",
"date": "2020-02-11T08:31:24"
} } }
If I try to set kScopes to https://graph.microsoft.com/User.Read I get an error:
Could not acquire token: Error Domain=MSALErrorDomain Code=-50000
"(null)" UserInfo={MSALErrorDescriptionKey=Authentication response
received without expected accessToken,
MSALInternalErrorCodeKey=-42008,
MSALCorrelationIDKey=FFCCD1D4-F0C8-46E6-85B2-A5642F1D4E1D}
How do I call the /me Microsoft Graph API with an access token from Azure B2C Active Directory?
You cannot use the Azure AD B2C issued access tokens to call Azure AD or Microsoft Graph API. You must have the user call your API, and your API needs to use client_credentials to obtain a token for Graph API using the Azure AD token endpoint of your B2C directory. Then the API can query Graph API for the user and return the result.
Azure AD B2C access tokens can only be used to access your own protected resources.

Defining the Authority

Currently I'm doing the following
services.AddAuthentication("Bearer")
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetValue<string>("IdentityServer:Authority");
options.RequireHttpsMetadata = Configuration.GetValue<bool>("IdentityServer:RequireHttps");
options.ApiName = Configuration.GetValue<string>("IdentityServer:UserManagementApi:ResourceName");
options.SaveToken = true;
})
.AddCookie(cfg => cfg.SlidingExpiration = true);
Where I'm getting the Authority from the config file. However, we are upgrading our app to be multi-tenant and need to set these values depending on the tenant being called. Is it possible to set these values from a service that will find the tenant at runtime using IOC.
We are using Dotnet Code 2.2 and Lamar IOC.
There is what appears to be a duplicate of this, however, I have the following issue as we are running a number of microservices.
Each Tenant has it's own IdentityServer4 tenant, that uses it's own database. This is a standalone service to manage the users.
Each Api uses it's own tenanted IdentityServer api to authenticate.
So
Api Site ----> connects to IdentityServer4 Site
eg.
api1.company1.com connects to auth.company1.com to authenticate
api2.company1.com connects to auth.company1.com to authenticate
api1.company2.com connects to auth.company2.com to authenticate
api2.company2.com connects to auth.company2.com to authenticate
Each Api will call other api's and pass the user token on to authenticate. I need a way to tell the api what auth to use to authenticate. Using a shared Authority, when getting the token you get the following
{
"nbf": 1556619889,
"exp": 1556623489,
"iss": "http://shared-domain-for-every-tenant,
"aud": [
"http://shared-domain-for-every-tenant/resources",
"api1",
"api2"
],
"client_id": "postman",
"scope": [
"api1",
"api1"
]
}

Resources