ASP.NET Core: Securing the ASP.NET Core API using Azure AD and User Access Tokens
In the previous tutorial, we have seen the ASP.NET Core application authentication using Azure AD. In this tutorial we will implement the ASP.NET Core API Authentication using Azure AD and Access tokens. Most of you those who have worked on ASP.NET Core API security, must have used JSON Web Token (JWT) for authentication. If you are not aware about JWT then you can read this tutorial. Generally, we use API to expose our application over publicly accessible endpoints to client application like browser based JavaScript e.g. Angular, React, then to mobile clients or even ASP.NET Core Web App, etc. In this case providing secure access to the API is recommended. If you choose to deploy the API on Microsoft Azure then, integrating Azure AD for API authentication will be the possible alternative. The figure 1 explains the approach of accessing and authenticating API using Azure AD
Figure 1: The access of Secure API
As explained in figure 1, the API and Client applications are registered as applications in Azure AD. The behaviour is explained using numbering matched with the figure
- The browser access the client App.
- The client app responds the Login page to the browser.
- Browser login ins using credentials
- These credentials are verified by the client app using Azure AD
- If the credentials are successful, the access token scope is issued to the client. The client uses this token to make call to the API
- API verifies the token using Azure AD
- If the token is verified then the API generates response
- The response is sent back to the browser.
Step 1: To implement the tutorial, I have created ASP.NET Core API project with Microsoft Identity Platform as shown in figure 2
Figure 2: The API Project creation
The API project is created with default WeatherForecastController. I have used it as it it. In the project, expand dependencies, here you will see that the project is already added with Identity packages as shown in figure 3
Figure 3: The Identity Packages
- Microsoft.AspNetCore.Authentication.JwtBearer: This package is an ASP.NET Core Middleware which is used to enable the application to receive an OpenID connect bearer token.
- Microsoft.AspNetCore.Authentication.OpenIdConnect: This package is an ASP.NET Core middleware which enables an application to support the OpenID Connect authentication workflow to authenticate the client application.
- Microsoft.Identity.Web: This package is used to enable the ASP.NET Core web apps and web APIs to use the Microsoft identity platform for security. This package is specifically used for web applications, which sign-in users, and protect web APIs, which optionally call downstream web APIs.
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Identity.Web.Resource;
using System;
using System.Collections.Generic;
using System.Linq;
namespace Core_API.Controllers
{
[Authorize]
[ApiController]
[Route("[controller]")]
public class WeatherForecastController : ControllerBase
{
private static readonly string[] Summaries = new[]
{
"Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" }; private readonly ILogger<WeatherForecastController> _logger; // The Web API will only accept tokens 1) for users, and 2)
// having the "access_api_user" scope for this API static readonly string[] scopeRequiredByApi = new string[]
{ "access_api_user" };
public WeatherForecastController (ILogger<WeatherForecastController> logger)
{
_logger = logger;
}
[HttpGet]
public IEnumerable<WeatherForecastController>Get()
{
HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
var rng = new Random();
return Enumerable.Range(1, 5).Select(index => new WeatherForecast
{
Date = DateTime.Now.AddDays(index),
TemperatureC = rng.Next(-20, 55),
Summary = Summaries[rng.Next(Summaries.Length)]
}).ToArray();
}
}
}
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "mydomain.onmicrosoft.com",
"TenantId": "11111111-8c95-4c81-97e5-d9400ddfcbbd",
"ClientId": "22222222-8a56-4b8b-b7c5-c8b16a960661",
"CallbackPath": "/signin-oidc"
}
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddCors(options =>
{
options.AddPolicy("cors",
policy =>{ policy
.AllowCredentials()
.SetIsOriginAllowedToAllowWildcardSubdomains()
.AllowAnyHeader()
.AllowAnyMethod();
});
});
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllers(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientSecret": "a2Z_v__6erSKeD8z-6jYENYd5K5cXE-X89",
"Domain": "sabnisthotmail.onmicrosoft.com",
"TenantId": "e4c947ee-8c95-4c81-97e5-d9400ddfsevr",
"ClientId": "eb28e5fb-cde4-45a3-9c7d-1111111",
"CallbackPath": "/signin-oidc",
"SignedOutCallbackPath ": "/signout-callback-oidc"
},
"ApiConfig": {
"AccessToken": "api://929434f4-8a56-4b8b-b7c5-1111111111/access_api_user",
"ApiBaseAddress": "https://localhost:44304/"
},
using Microsoft.Extensions.Configuration;
using Microsoft.Identity.Web;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Threading.Tasks;
namespace Core_ClientApp.Services
{
public class ServiceClient
{
private readonly IHttpClientFactory httpClient;
private readonly ITokenAcquisition token;
private readonly IConfiguration cfg;
public ServiceClient(IHttpClientFactory client,
ITokenAcquisition token,
IConfiguration cfg)
{
this.httpClient = client;
this.token = token;
this.cfg = cfg;
}
public async Task<string> GetDataAsync()
{
try
{
// create HTTP Client
var client = httpClient.CreateClient();
// read api token
var apiToken = cfg["ApiConfig:AccessToken"];
// get access token for authenticated user
var accessToken = await token.GetAccessTokenForUserAsync(new[] { apiToken });
// read the service base adddress and define HTTP Request details
client.BaseAddress = new Uri(cfg["ApiConfig:ApiBaseAddress"]);
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);client.DefaultRequestHeaders.Accept
.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// make call to service
var response = await client.GetAsync("weatherforecast");
if (response.IsSuccessStatusCode)
{
// generate response
var receivedData = await response.Content.ReadAsStringAsync();
return receivedData;
}
// if error occured then return the error
throw new ApplicationException($"Status code: {response.StatusCode}, Error Message: {response.ReasonPhrase}");
}
catch (Exception e)
{
throw new ApplicationException($"Error {e}");
}
}
}
}
Listing 5: The call to APIpublic void ConfigureServices(IServiceCollection services)
{
services.AddTransient<ServiceClient>();
services.AddHttpClient();
services.AddOptions();
// get the access token
var token = Configuration.GetValue<string>("ApiConfig:AccessToken")?.Split(' ');
//configure the Microsoft Identity and enable token to access the API by passing
// token
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(token)
.AddInMemoryTokenCaches();
// Add Authorization Policy to Razor views
services.AddRazorPages().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
}
[AuthorizeForScopes(Scopes = new string[] { "api://929434f4-8a56-4b8b-b7c5-c8b16a960661/access_api_user" })]
public class ClientModel : PageModel
{
private readonly ServiceClient service;
public string ResponseData { get; set; }
public ClientModel(ServiceClient serv)
{
service = serv;
}
public async Task OnGetAsync()
{
ResponseData = await service.GetDataAsync();
}
}
Listing 7: The ClientModel code
@page
@model Core_ClientApp.Pages.ClientModel
@{
}
<h1>Making Secure call to API Protected using Azure AD</h1>
<h2>The response is</h2>
<hr/>
<div>
<strong>
@Model.ResponseData
</strong>
</div>
Listing 8: The Client UI

















