This article shows how to secure and use different APIs in an ASP.NET Core API which support OAuth access tokens from multiple identity providers. Access tokens from Azure AD and from Auth0 can be be used to access data from the service. Each API only supports a specific token from the specific identity provider. Microsoft.Identity.Web is used to implement the access token authorization for the Azure AD tokens and the default authorization is used to support the Auth0 access tokens.
Code: https://github.com/damienbod/SeparatingApisPerSecurityLevel
Blogs in this series
- Securing OAuth Bearer tokens from multiple Identity Providers in an ASP.NET Core API
Setup
An API ASP.NET Core application is created to implement the multiple APIs and accept access tokens created by Auth0 and Azure AD. The access tokens need to be validated and should only work for the intended purpose for which the access token was created. The Azure AD API is used by an ASP.NET Core Razor page application which requests an user access token with the correct scope to access the API. Two Azure AD App registrations are used to define the Azure AD setup. The Auth0 application is implemented using a Blazor server hosted application and accesses the two Auth0 APIs, See the pervious post for details.
To support the multiple identity providers, multiple schemes are used. The Auth0 APIs use the default scheme definition for JWT Bearer tokens and the Azure AD uses a custom named scheme. It does not matter which scheme is used for which as long as the correct scheme is defined on the controller securing the API. The AddMicrosoftIdentityWebApiAuthentication method takes the scheme and the configuration name as a optional parameter. The Azure AD configuration is defined like any standard Azure AD API in ASP.NET Core.
public void ConfigureServices(IServiceCollection services) { // Adds Microsoft Identity platform (AAD v2.0) // support to protect this Api services.AddMicrosoftIdentityWebApiAuthentication( Configuration, "AzureAd", "myADscheme"); // Auth0 API configuration=> default scheme services.AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; }).AddJwtBearer(options => { options.Authority = "https://dev-damienbod.eu.auth0.com/"; options.Audience = "https://auth0-api1"; }); services.AddSingleton<IAuthorizationHandler, UserApiScopeHandler>(); // authorization definitions for the multiple Auth0 tokens services.AddAuthorization(policies => { policies.AddPolicy("p-user-api-auth0", p => { p.Requirements.Add(new UserApiScopeHandlerRequirement()); // Validate id of application for which the token was created p.RequireClaim("azp", "AScjLo16UadTQRIt2Zm1xLHVaEaE1feA"); }); policies.AddPolicy("p-service-api-auth0", p => { // Validate id of application for which the token was created p.RequireClaim("azp", "naWWz6gdxtbQ68Hd2oAehABmmGM9m1zJ"); p.RequireClaim("gty", "client-credentials"); }); }); services.AddControllers(options => { var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(policy)); }); }
The Configure method uses the UseAuthentication method to add the middleware for the APIs.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... app.UseRouting(); app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); }
The AzureADUserOneController class is used to implement the API for the Azure AD access tokens. The AuthorizeForScopes attribute from Microsoft.Identity.Web is used to validate the Azure AD App registration access token and define the scheme required for the validation. The scope name must match the Azure App registration definition.
using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using Microsoft.Identity.Web; namespace MyApi.Controllers { [AuthorizeForScopes(Scopes = new string[] { "api://72286b8d-5010-4632-9cea-e69e565a5517/user_impersonation" }, AuthenticationScheme = "myADscheme")] [ApiController] [Route("api/[controller]")] public class AzureADUserOneController : ControllerBase { private readonly ILogger<UserOneController> _logger; public AzureADUserOneController(ILogger<UserOneController> logger) { _logger = logger; } [HttpGet] public IEnumerable<string> Get() { return new List<string> { "AzureADUser one data" }; } } }
The UserOneController implements the Auth0 user access token API. Since the default scheme is used, no scheme definition is required. The authorization policy is used to secure the API which validates the scope and the claims for this API.
using System.Collections.Generic; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; namespace MyApi.Controllers { [Authorize(Policy = "p-user-api-auth0")] [ApiController] [Route("api/[controller]")] public class UserOneController : ControllerBase { private readonly ILogger<UserOneController> _logger; public UserOneController(ILogger<UserOneController> logger) { _logger = logger; } [HttpGet] public IEnumerable<string> Get() { return new List<string> { "user one data" }; } } }
When the API application is started the APIs can be used and a swagger UI implemented using Swashbuckle was created to display the different APIs. Each API will only work with the correct access token. The different UIs can use the APIs and data is returned.
Links
https://auth0.com/docs/quickstart/webapp/aspnet-core
https://docs.microsoft.com/en-us/aspnet/core/security/authorization/introduction
Open ID Connect
Securing Blazor Web assembly using Cookies and Auth0
This content was originally published here.