Month: November 2017

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.

Testing a B2C secured Web API using Postman

In my post yesterday on Securing a Web API, I asked how might I test my API after securing it, since I didn’t have a client app created yet.

Of course, there is a way using Postman.

I’ve mentioned previously how to use the “run now” policy feature to test your policy and review your tokens. That previous blog post used a web application. Today’s blog posts uses a native app as the selected application.

Go to your B2C_1_SiUpIn policy:

  1. Under Select application, chose Hello API WPF App. 
  2. Under Select reply url, choose the https url
  3. Expand Access Tokens and under Select resource, select Hello API (or whateer name you gave your API from yesterday’s blog post). Make sure all the scopes are selected.

Here’s what my Run Policy Settings look like.

Sign Up or Sign In policy run now page
Click Run now. You’ll see a new browser tab appear. Copy the resulting URL and open up your favorite flavor of notepad.

access token in the reply url

Make sure you only copy the access token and none of the other parameters that come after the access_token. This had me scratching my head for a good half-hour.

Open up https://jwt.ms and paste in the access token to confirm it all looks good and contains the claims you’re Hello API is expecting, e.g. “read”

reviewing scopes in jwt.ms

Next, fire up Postman. Also, make sure your Hello API project is running 🙂

I’m still new to Postman, so YMMV. If you have a different way of using Postman for this scenario, please let me know!

  1. Choose GET and insert the URL for your Hello API /hello endpoint.
  2. Under Headers, type in Authorization
  3. For its value, type in Bearer then the access token.

Postman with a bearer tokenAnd if all is setup correctly, you’ll get the expected response!

I’m still working on a blog post on how to debug all these steps for creating a client application and an API for use with B2C. Stay tuned!

Securing and calling a web API using Azure AD B2C

This blog post walks you through creating and securing an ASP.NET Web API to work with B2C. You’ll call your new API from an existing B2C sample WPF application. I chose the WPF application example since this type of client app requires the fewest line changes to setup.

Note: The Azure Docs are securing a web API and calling a web API. This blog post is my “if I could go back in time, here’s what I would tell myself.”

When I first started learning Azure AD B2C, I thought it was adequate for 100 lv content that the samples to only contain a client application to obtain an id token. Perhaps this makes sense for the very first module in a B2C course. However, most apps want to do more than just authenticate and show your display name from the id_token. If you want to do more than just greet your users, your client app need to acquire an access token so it can put it to use, e.g. accessing some resource like an API.

btw: if you are only using an id token, I’d love to learn more about your scenarios!

Note you have to register your own APIs with B2C. You can’t use someone else’s API for demo purposes because the client ID and tenant name would be different. Trust me I went down this path several times before the “oh yeah – that’s right” moment finally hit home and stuck with me.

Demo code

The API in this sample returns a Hello <username> if the user is authorized.

The code sample that goes alongside this blog post is at https://github.com/saraford/HelloAPI-blog-post

Creating the Web API

  1. Open a new instance of Visual Studio. (In this example, I’m using Visual Studio 2017, but if there’s interest for 2015 steps, lmk!)
  2. Go to File – New – Project and choose Web – ASP.NET Web Application (.NET Framework)
  3. Choose Web API
  4. Name your project HelloAPI and click OK
  5. Leave authentication as No Authentication – otherwise, it installs Microsoft.Owin.Security.ActiveDirectory that uses the WindowsAzureActiveDirectoryBearerAuthentication middleware that doesn’t work with B2C tokens.

Create a route

Before we jump into authorizing our route, let’s make sure our API works properly.

  1. On the Controllers folder, right-click and select Add – Controller 
  2. Select Web API 2 Controller – Empty and then click Add
  3. Name the controller HelloController

Next, we’ll add a new route


[Route("hello")]
[HttpGet]
public string Hello()
{
   return "Hello there!";
}

Now run your application. Add the `/hello` endpoint in your browser. You’ll be prompted to open a “hello.json” file.

For my setup, this file opens in VS Code and I see “Hello there!” returned.

Configure SSL

  1. Open the Properties Tool Window for the project, i.e. go to View – Properties Window (or press F4 on the project in solution explorer)
  2. Set SSL Enabled to true
  3. Copy the resulting SSL Link that appears below
  4. Go to the Properties page by right clicking on the project name in solution explorer and selecting Properties (or pressing Alt+Enter)
  5. On the Web side-bar tab, paste in the SSL link in the Project Url
  6. Click Create Virtual Directory – this step may be optional, I’m not sure.

Run your project and hit the /hello endpoint just to verify you are all set with SSL.

Now that the API is working properly, we can now move onto the next step: authorizing only those users with certain scopes in their access tokens to access the API.

Register the API in Azure AD B2C

Although you can register the API in the Azure portal at any time, I prefer to create the project first. It just keeps me grounded as to what I’m doing.

  1. Go to the Azure Portal – https://portal.azure.com/
  2. Make sure you are in the correct tenant you want to use.
  3. Register a new Web API
    1. Click Applications –  Add.
    2. Give it a reader-friendly name, like `Hello API for Demo`
    3. Click Yes for Web App / Web API
    4. For Reply URL, paste in the SSL URL you are now using from the Properties page (the Alt+Enter page) along with the endpoint e.g. `https://localhost:44326/hello`
    5. For App ID URI, provide a unique ID, e.g. `helloAPI` – this must be unique across all your B2C-registered applications in your tenant.
    6. Keep Native client as No.
    7. Click Create and wait a couple of seconds.
  4. Add scopes
    1. Click on your newly registered application in the portal, i.e. `Hello API for Demo`
    2. Click Published scopes 
    3. Under Scope, give it the name `read` and a description `yet another read access`

If you have any issues following these steps, please check out my previous blog post.

Install the middleware 

Back to Visual Studio!

The next steps are about setting up the middleware – aka the libraries for the Web API that talk to B2C, parse tokens, etc.

Note: OWIN is a specification, whereas “Katana” is the implementation of OWIN for .NET, but it seems a lot of people say “OWIN” to represent the implementation that is Katana.

You install all the necessary OWIN / Katana packages from the Package Manager Console. The blog post uses the nuget command line.

Note: if you decided to keep it all into one big solution, you’ll need to specify the project name (`Install-Package Microsoft.Owin.Security.OAuth -ProjectName HelloAPI`) or switch to that project in the nuget Package Manager Console dropdown. This got me a few times at first. I didn’t realize I was installing to the wrong project.

1. Run the command

Install-Package Microsoft.Owin.Security.Oauth

This will install at least the following dlls:

  • Owin
  • Microsoft.Owin
  • Microsoft.Owin.Security
  • Microsoft.Owin.Security.Oauth
  • And possibly others

2. Run the command

Install-Package Microsoft.Owin.Security.Jwt

This will install “system.identitymodel.tokens.jwt” – this is a lower layer component that verifies tokens.

3. Run the command

Install-Package Microsoft.Owin.Host.SystemWeb

This loads the OWIN startup.cs into pipeline. Otherwise, you’ll get the dreaded “unauthorized” generic error message.

4. Run the command

Install-Package Microsoft.Owin.Security.OpenIdConnect

This is needed for the OpenIdConnectCachingSecurityTokenProvider class, which is needed for refreshing B2C tokens. You’ll add this class further below.

Add authorization code to the API

To the code!

Next, let’s setup the OWIN middleware.

1. The OWIN middleware needs to be configured at Startup. Otherwise, you’ll get the error:

[EntryPointNotFoundException: The following errors occurred while attempting to load the app.
 - No assembly found containing an OwinStartupAttribute.
 - No assembly found containing a Startup or [AssemblyName].Startup class.

This was a helpful article: OWIN Startup Class Detection

2. At the root project level in Solution Explorer, right-click and Add a new class. Choose OWIN Startup class. Rename the class to Startup.cs

3. Add the following code to the constructor:

// The OWIN middleware will invoke this method when the app starts
public void Configuration(IAppBuilder app)
{
  ConfigureAuth(app);
}

4. Change the class to partial

public partial class Startup

 

5. Go to the App_Start folder and add a new class called OpenIdConnectCachingSecurityTokenProvider.cs. Paste the code from an existing sample. Make sure to change the namespace to match yours. This class is needed to refresh B2C tokens.

6. In the App_Start folder and add a new class called `Startup.Auth.cs`

  • Change this to partial. Why a partial class? It seems to be the design standard I’m seeing other samples use, but there’s no reason you can’t keep it all in the Startup.cs class (not that I’m aware of).
  • Change the namespace from `HelloAPI.App_Start` to `HelloAPI` to match the Startup.cs class
  • Add the following code into your Startup.Auth.cs class to configure the OWIN middleware (notice how this code uses your newly added OpenIdConnectCachingSecurityTokenProvider)
public partial class Startup
{
  // These values are pulled from web.config
  public static string AadInstance = ConfigurationManager.AppSettings["ida:AadInstance"];
  public static string Tenant = ConfigurationManager.AppSettings["ida:Tenant"];
  public static string ClientId = ConfigurationManager.AppSettings["ida:ClientId"];
  public static string SignUpSignInPolicy = ConfigurationManager.AppSettings["ida:SignUpSignInPolicyId"];
  public static string DefaultPolicy = SignUpSignInPolicy;

  /*
  * Configure the authorization OWIN middleware
  */
  public void ConfigureAuth(IAppBuilder app)
  {
    TokenValidationParameters tvps = new TokenValidationParameters
    {
      // Accept only those tokens where the audience of the token is equal to the client ID of this app
      ValidAudience = ClientId,
      AuthenticationType = Startup.DefaultPolicy
     };

     app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
     {
         // This SecurityTokenProvider fetches the Azure AD B2C metadata &amp;amp;amp;amp; signing keys from the OpenIDConnect metadata endpoint
         AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(String.Format(AadInstance, Tenant, DefaultPolicy)))
     });
   }
}

Now ctrl+. All the things! Or simply just use these using statements in Startup.Auth.cs

using HelloAPI.App_Start;
using Microsoft.Owin.Security.Jwt;
using Microsoft.Owin.Security.OAuth;
using Owin;
using System;
using System.Configuration;
using System.IdentityModel.Tokens;

7. In Web.Config, add the following keys to your <appSettings>

<appSettings>
 <add key="ida:AadInstance" value="https://login.microsoftonline.com/{0}/v2.0/.well-known/openid-configuration?p={1}" />
<add key="ida:Tenant" value="dorfarasB2CTenant.onmicrosoft.com" />
<add key="ida:ClientId" value="b32e98b0-16ec-46ae-9e6a-4ccb0286e9a3" />
<add key="ida:SignUpSignInPolicyId" value="b2c_1_SiUpIn" />

Let’s pause here and make sure everything runs. We just want to make sure the OWIN middleware is configured properly to run at startup.

Add authorization verification to the route

1. In HelloController.cs, add the following constants at the top of the class

 // OWIN auth middleware constants
 public const string scopeElement = "http://schemas.microsoft.com/identity/claims/scope";
public const string objectIdElement = "http://schemas.microsoft.com/identity/claims/objectidentifier";

2. Secure your route by adding the [Authorize] attribute. I got this information from the ASP.NET Web API 2 attribute routing docs.

[Authorize]
[Route("hello")]
[HttpGet]
public string Hello()

3. Add the code to verify the access token contains the correct scopes. And if so, might as well display the name while we’re at it!

public string Hello()
{
   HasRequiredScopes("read");
   string name = ClaimsPrincipal.Current.FindFirst("name").Value;
   return "Hello there! " + name;
}

4. Add the HasRequiredScopes() method below the Hello() method – this is the method the B2C samples are using right now. YMMV.

// Validate to ensure the necessary scopes are present.
private void HasRequiredScopes(String permission)
{
  if (!ClaimsPrincipal.Current.FindFirst(scopeElement).Value.Contains(permission))
  {
     throw new HttpResponseException(new HttpResponseMessage
     {
       StatusCode = HttpStatusCode.Unauthorized,
       ReasonPhrase = $"The Scope claim does not contain the {permission} permission."
     });
  }
}

5. ctrl+. to add `using System.Security.Claims;` for the ClaimsPrincipal object

Let’s run your project to verify everything builds and launches. Don’t try to hit your endpoint yet. You are not authorized yet so it won’t work.

BTW: if someone knows a better way to test everything at this point, please let me know! 
Update: Here’s a new blog post how to test your new API using Postman

Setting up the client to talk to your new API Endpoint

  1. Download or clone the B2C sample WPF project from https://github.com/Azure-Samples/active-directory-b2c-dotnet-desktop
  2. Register this in the Azure Portal (if you’ve already registered this from a previous sample, you can still use that registration)
    1. Make sure you are in the right tenant!
    2. Go to Application – Add
    3. Give your WPF app a friendly name, e.g. ​`​Hello API WPF App`
    4. Click No for Web App / Web API
    5. Click Yes for Native client
    6. Give it a custom Redirect URI of `com.onmicrosoft.<your-tenant-name>.desktopapp://redirect/path`
    7. Click Create
    8. Wait a couple of seconds and open your app Hello API WPF App registration page
    9. Click on API access (preview) then click Add
    10. Select your HelloAPI in the dropdown and make sure the `read` scope is selected
    11. Click OK
  3. In App.xaml.cs
    1. Update the Tenant name to use your tenant name
    2. Update the ClientId to match your Application ID you got from registering your app in the portal
    3. Update your policies to match the ones you’ve created from my previous blog posts.
  4. In App.xaml.cs update the ApiScopes and ApiEndpoint to use you API endpoint and your defined scope, e.g.

 

public static string[] ApiScopes = { "https://dorfarasB2CTenant.onmicrosoft.com/helloAPI/read" };
public static string ApiEndpoint = "https://localhost:44326/hello";

Note: I like to comment out the previous ApiScopes and ApiEndpoint, so in case I need to debug, it’s easy to switch back to a working version.  Note: I like to comment out the previous ApiScopes and ApiEndpoint, so in case I need to debug, it’s easy to switch back to a working version.

5. Run the client app. (Sign out if applicable) and sign in. Now click Call API, and you should see “Hello there! <Display name!>”

Reference

Here’s my API Registration in case it helps you to debug.

Hello API registration in the Azure Portal with Reply URL showing the SSL URL with endpoint and the App ID URI of `helloAPI`

Achievement unlocked: Since I’m historically a client developer, this is my first time blogging about creating an API!