Securing an ASP.NET Core Web API 2 using Azure AD B2C

In a previous post you saw how to secure and call an ASP.NET Web API using Azure AD B2C. Today’s post is how to secure an ASP.NET Core Web API 2. This blog post walks you through the steps from File – New – Project to using Postman to test your API with an access token. Here’s the official ASP.NET Core sample

Create the project

Since this is my first ASP.NET Core project, I spent some time playing with the getting started first-web-api tutorials.

  • File – New – Project – .NET Core – ASP.NET Core Web Application
  • I called mine HelloCoreAPI, because in my previous post I had registered a HelloAPI and you need to use an unique App ID URI
  • Select Web API
    • Make sure ASP.NET Core 2.0 is selected (I’m using VS 2017 so it’s the default)
    • Make sure No Authentication is set

And hit F5 just to make sure things are working.

Create the /hello endpoint

I spent some time exploring the documentation on ASP.NET Core routing.

  • I deleted the default ValuesController since I won’t be using it
  • Right click on the Controllers folder, and add a new API Controller – Empty
  • Call it HelloController – just like picking up your mouse and saying “Hello Computer!”

I’ve setup my route to look like

 

[Route("hello")]
[HttpGet]
public IActionResult Hello() {
    return Ok("Hello There");
}
  • Open launchSettings.json and for both profiles (or just the IIS Express profile) change “launchUrl” to “hello” – the preceding ‘/’ is handled by the “ApplicationURL”

Hit F5 and see “Hello there!”

Enable https

I happened to see this on twitter today while writing this blog post. For cross-platform support, check out this blog post: https://blogs.msdn.microsoft.com/webdev/2017/11/29/configuring-https-in-asp-net-core-across-different-platforms/

  • Go to Project Properties – Debug and check Enable SSL
  • In `launchSettings.json` change both the `applicationURL` to the new https address

According to the enforcing SSL docs, you should also do

services.Configure<MvcOptions>(options =>
{
     options.Filters.Add(new RequireHttpsAttribute());
});

F5 to make sure everything’s good

Register the API application in B2C

  • Go to the Azure Portal and make sure you are in the right tenant!
  • In B2C, go to Applications and click New Application
  • Give the application a name you can identify, like Hello Core API
  • This is a Web API so click Web API
  • Under reply URL use the https address that opens when you hit F5, e.g. https://localhost:44332
  • Under API ID URI, give it a unique name. Since I have a previous example, I shall call this API, creatively, HelloCoreAPI
  • Hit create
  • Create the “demo.read” scope under Published Scopes

Enter the B2C coordinates

In appsettings.json add the following:

"AzureAdB2C": 
  {
    "Tenant": "<your tenant name>.onmicrosoft.com", 
    "ClientId": "<the application id you just registered>",
    "Policy": "<your sign up sign in policy e.g. B2C_1_SiUpIn>",
    "ScopeRead": "demo.read"  
  },

Setup Middleware

  1. in Startup.cs – ConfigureServices(), we’ll want to initialize the JWT Bearer middleware to validate tokens since this is an ASP.NET Core Web API.
public void ConfigureServices(IServiceCollection services) {
  services.AddAuthentication(options => {
    options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
    })
      .AddJwtBearer(jwtOptions => {
         jwtOptions.Authority = $"https://login.microsoftonline.com/tfp/{Configuration["AzureAdB2C:Tenant"]}/{Configuration["AzureAdB2C:Policy"]}/v2.0/";
         jwtOptions.Audience = Configuration["AzureAdB2C:ClientId"];
         jwtOptions.Events = new JwtBearerEvents
         {
            OnAuthenticationFailed = AuthenticationFailed
         };
    });

  services.AddMvc();
}

2. In Startup.cs, add the AuthenticationFailed method for locally debugging this sample only. Note how the repo readme instructs you not to use this handler in production scenarios.

3. In Startup.cs, create a public static field called ScopeRead

4. In Startup.cs Configure() add

ScopeRead = Configuration["AzureAdB2C:ScopeRead"];
app.UseAuthentication(); 
...
app.UseMVC();

Secure the API call itself

  1. Add the Authorize attribute to the class level in HelloController.cs
[Authorize]
public class HelloController : Controller

2. Make sure the user has the right scopes for the API.

public IActionResult Hello() {

  var scopes = HttpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/scope")?.Value;
  if (!string.IsNullOrEmpty(Startup.ScopeRead) && scopes != null && scopes.Split(' ').Any(s => s.Equals(Startup.ScopeRead))) {
     return Ok("Hello There");
  }
  else
  {
     return Unauthorized();
  }
 }

Whenever I’m debugging a B2C call to an API, I set a breakpoint inside the API to make sure I know which Unauthorized I’m debugging.

If you hit F5, you’ll get a 401 unauthorized error, because this user agent isn’t authorized, so any breakpoints you set within the method won’t get hit.

How to test your API

From a previous blog post, you can use the Policy Run Now feature and Postman to debug.

My code sample is at https://github.com/saraford/HelloCoreAPI

My API app registration in the B2C portal looks like

ASP.NET Core API app registration in B2C

Next blog post: How to call this ASP.NET Core Web API from a Single Page Application.

4 thoughts on “Securing an ASP.NET Core Web API 2 using Azure AD B2C

  1. Can you talk more about:

    jwtOptions.Authority = $”https://login.microsoftonline.com/tfp/{Configuration[“AzureAdB2C:Tenant”]}/{Configuration[“AzureAdB2C:Policy”]}/v2.0/”;
    jwtOptions.Audience = Configuration[“AzureAdB2C:ClientId”];

    What is the authority? Why does it matter?

    Can you talk about why the Audience matters? And how it relates to the JWT created by B2C?

    Like

  2. I’ve also seen code that use:

    jwtOptions.MetadataAddress = “https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}

    Which one should I use? And why are there 2 urls to get the openid-configuration?

    Like

Leave a comment