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"
]
}
Related
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
I have a Web API "App Registration" called "BackEnd_API" which defines some Application Roles and User Roles.
{
"allowedMemberTypes": [
"Application"
],
"description": "resource.READ allows you read access to all items in the application",
"displayName": "resource.READ",
"id": "9650cfb9-570d-4b79-1337-a01337ed6c29",
"isEnabled": true,
"lang": null,
"origin": "Application",
"value": "resource.READ"
},
I then have another Client Application "App Registration" called "Client_App" which consumes that API to which i've assigned the AppRoles "resource.READ" using either Azure_CLI or PowerShell.
In the Azure Portal I can see that the Service Principal is assigned the role.
When i use the Client_Credentials Flow the resulting access token DOES contain that Roles claim which i use on the BackEnd to authorize the caller.
Until Here ALL Good.
Now, I want to consume the same Web API "BackEnd_API" from another Consuming Application using Managed Identities. So I've created another "App Service", enabled System Assigned Identity and assigned the AppRoles "resource.READ" using Azure CLI.
In the Azure Portal I can see that the Service Principal is assigned the role.
I can get a Token using the JS Azure SDK.
var withClientSecretCredential = () => {
require("#azure/core-auth");
require('dotenv').config()
const {
ManagedIdentityCredential
} = require("#azure/identity");
const logger = require('#azure/logger');
logger.setLogLevel('info');
// Load the .env file if it exists
const credentials = new ChainedTokenCredential(
new ManagedIdentityCredential("54e5c672-872f-4866-b067-132973cb0c91"),
);
token = credentials.getToken(['api://e22fd9eb-3088-4155-936a-0919681c3eb5/.default']);
return token
But the received token in this case has no 'role' claims, so the API call fails to authorize.
I double checked roles and assignment all looks good; is this supposed to work ?
Token without 'role' claim.
{
"aud": "e22fd9eb-3088-4155-936a-0919681c3eb5",
"iss": "https://login.microsoftonline.com/45591230-6e37-4be7-acfb-4c9e23b261ea/v2.0",
"iat": 1634550153,
"nbf": 1634550153,
"exp": 1634636853,
"aio": "E2ZgYGguYd9fNkv3pOV5Iduv2655AgA=",
"azp": "7dd894ca-6c1b-45ae-b67c-75db99593a14",
"azpacr": "2",
"oid": "54e5c672-872f-4866-b067-132973cb0c91",
"rh": "0.ARAAYH9ZRTdu50us-0yeI7Jh6sqU2H0bbK5Ftnx125lZOhQQAAA.",
"sub": "54e5c672-872f-4866-b067-132973cb0c91",
"tid": "45597f60-6e37-4be7-acfb-4c9e23b261ea",
"uti": "qOLzTFlmw0yuWeFXXT1pAA",
"ver": "2.0"
}
Thanks for helping.
The token you might be getting should be an access token and the roles you are looking for should be in the id token. Did you tried this by enabling "ID Token" in azure portal?
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.
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"
},
...
}
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}"
};
});