Contents

Protect Web API with Azure AD B2C authorization

Azure AD B2C Part 4

Protecting your APIs from unauthorized access is important. Azure AD B2C helps you to validate the user or app accessing the API and allows access to the API only with authorized token obtained fromt AD B2C. Welcome to part 4 of a series covering Azure AD B2C features. In this post, I show how to protect your backend Web API with a valid access token obtained from AD B2C.

Here are the earlier posts in this series.

  1. Setting up a tenant in Azure AD B2C
  2. Creating and testing user flows in Azure AD B2C
  3. Use Azure AD B2C in a React JS SPAB2C

Introduction

If you have followed the earlier posts, we have a Single Page Application in React JS which can let users sign-up and sign-in to the app using AD B2C. In many real world applications, you will have some backend API serving your data. We want that only this React app that we developed should be able to acess the API on behalf of the signed in users. This will then protect the API from unauthorized access. Let’s see how to do that in this post.

How does this work?

Azure AD B2C supports Open ID Connect protocol (OIDC) for identification and OAuth2 for authorization. Once a user is signed in to your app, the app then requests an access token from Azure AD B2C. The access token is a JSON Web Token (JWT) signed by AD B2C and is valid for a certain period of time. It contains required scopes and other claims or attributes. Scopes are akin to permissions required to access the API. Client app uses this token as Bearer authorization when calling your protected API. The API configured to be protected with AD B2C, ensures validity and integrity of the token and that it contains required permissions to access that API and then allows the API to be executed.


/images/azure-ad-b2c/adb2c-api/adb2c-api-how-this-works.svg
Protecting API with AD B2C


In order to get this working, we first need to set up the AD B2C tenant with the app registrations, configure API permissions for the app registrations and then configure the client and API app code to consume this configuration. Sample code for both client and API app are provided in this post. Let’s see the steps in detail and get everything working.

Configure Azure AD B2C

We need to have two registrations in the AD B2C tenant for representing our API app and the front-end React JS client. We can reuse the client app we created earlier and configure it to allow access to APIs. In this post however, we will create these two app registrations to help you follow along.

If you have not done already, create an AD B2C tenant in Azure following instructions in this blog post.

Create API app registration in AD B2C

The API app registration represents our backend APIs. Let’s create a new app registration in the AD B2C tenant. Once the app registration is created, we will expose API permissions from this app.

Navigate to the App Registrations section and create a new app registration.


/images/azure-ad-b2c/adb2c-api/api-app-reg-1.png
App registrations

Give some name to the app. We will not add any redirect URI here as we are not going to sign-in or redirect from the API app. Leave other settings to default and click on Register.


/images/azure-ad-b2c/adb2c-api/api-app-reg-2.png
Create new app registration

Expose API

API app in AD B2C allows you to expose the APIs using API scopes. Using these scopes you can protect your API app such that any call to the actual API must have a valid token which includes the required scopes. AD B2C issues the tokens which can include the required scopes.

In order to configure the scopes, first create a URI for your API app. This URI is only a unique identification for your API app within AD B2C. It is not related to the actual URI / URL of your API app running somewhere in cloud or your local system.

To create an URI for your API app, navigate to your API app registration and then click on Expose an API. You will see that the Application ID URI is not configured. Click on Set link just near the application ID URI.


/images/azure-ad-b2c/adb2c-api/api-set-uri-1.png
API app URI

You can accept the random GUID value that appears in the URI field or you can change it as per your choice. As I said earlier, this is a unique identifier for your API app. In future if you create multiple API app registrations, it can be helpful to choose a value here to uniquely identify your app. Click on Save.


/images/azure-ad-b2c/adb2c-api/api-set-uri-2.png
Configure API app URI

Once the URI is configured, you can start adding scopes. The API app that we will use has only one API. Source code for this app is provided on GitHub linked later in this post. We will represent this API by adding a scope called as read_data.

Scope Based Access Control
You can protect your APIs by authorizing access with tokens containing one or more specified scopes. This also enables you to protect different APIs with different scopes. You can create different client app registrations and configure them to allow permissions to access only a subset of your APIs. This way, you can implement scope based access control. As an example, you may have a user facing app accessing APIs serving only the user related data and you can have an admin app allowed to access privileged data about the users.
Role Based Access Control (RBAC)?
While you can have different API permissions for different client apps, it is not directly feasible in AD B2C to do the same for different users. Imagine you have different users in your tenant and you would want to have some users access only the read APIs while some privilged users to access the write APIs. This role based access control isn’t possible out of the box with AD B2C.

Click on + Add a scope. In the ‘Add a scope’ dialog, give some name to the scope.

As mentioned earlier, scope is essentially a permission to access the APIs. This permission is granted to the client app. When the client app accesses an API, it can do so on behalf of the user. This is called delegated authorization. Effectively, the client app is acting as the user. In other words, user delegates permission to the client app to access API on their behalf.

Users or admins of the tenant need to provide their consent for a client app to access the API resources on their behalf. The Admin consent display name and Admin consent description are used to display information to the admins so they can grant access tenant wide on behalf of all users.

Make sure the state is set to ‘Enabled’ and then click on the button Add scope.


/images/azure-ad-b2c/adb2c-api/api-add-scope-1.png
Add API Scope


Your API app configuration is now complete.


/images/azure-ad-b2c/adb2c-api/api-add-scope-2.png
Completed API scopes


Create Client app registration in AD B2C

Similar to the creation of API app registration, create a new app registration for the front-end client app. Give some name to the app and make sure to add a redirect URI here with the type as ‘Single-page application (SPA)' and the address as http://localhost:3000. For the supported account types, choose the option for ‘Accounts in any identity provider or organizational directory’. This makes our app available to users of the active directory itself as well as we can provision users from other social identity providers such as Facebook or GitHub. Leave other settings to their default values and click on Register.


/images/azure-ad-b2c/adb2c-api/spa-app-reg-1.png
Create client app registration


If you already have an existing client app in your tenant, you can also reuse that and add API permissions as shown in the next step

Add API permissions for client app

We need to allow this client app to access the APIs from our API app. This is done by adding API permissions. In the app registration for the SPA app, navigate to API permissions and then click on + Add a permission. In the pop-up that appears on right side, navigate to My APIs. You should see the API app that we added earlier with the unique identifier. Select the API app.


/images/azure-ad-b2c/adb2c-api/spa-app-add-api-permission-1.png
Available API apps


It should now show the APIs that this API app has exposed for client apps to consume. We added only one API scope as read_data so you should be seeing that in the permissions list. Select that permission read_data and click on Add permissions button.


/images/azure-ad-b2c/adb2c-api/spa-app-add-api-permission-2.png
API app and permissions


The permission that we are adding for our app to access backend API is called as a delegated permission. The app uses this permission to access backend APIs on behalf of the user which means that the user effectively delegates the permission to access the resource to the app. For delegated permissions like this, we need to provide admin consent before the app can use that permission. You can read more about permissions and consents here.

Click on Grant admin consent and then confirm by clicking on Yes in the popup.


/images/azure-ad-b2c/adb2c-api/spa-app-add-api-permission-3.png
Admin consent for API permissions


Your client app configuration is now complete.


/images/azure-ad-b2c/adb2c-api/spa-app-add-api-permission-5.png
Completed API permissions for client app


Let’s see it in action

With the AD B2C setup, we can now run our Web API and client app to see the action.

Get the code

Code for this tutorial is available on GitHub. This repository contains two folders - dotnet-api and react-app. The dotnet-api folder contains the Web API code and the react-app folder contains the client app code. Clone the repository to your machine and follow the instructions below to run the code.

Run API

First, run the API code. The API app is a sample ASP.NET app with a Web API controller. It exposes only one endpoint /weatherforecasts and returns random weather data. In your terminal window, navigate to the dotnet-api folder to run the API app.


1
2
cd dotnet-api
dotnet run

This should start running the API app and you should see output like this in your terminal.

1
2
3
4
5
6
7
Building...
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: https://localhost:5001
info: Microsoft.Hosting.Lifetime[0]
      Now listening on: http://localhost:5000
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.


At this stage if you try to call the API endpoint for this app, you will get an error as 401 Unauthorized. Let’s try that using Postman tool.


/images/azure-ad-b2c/adb2c-api/api-pm-call-unauth.png
API call without token

Run Client

Now, let’s run the client code. This client app is again a simple ReactJS app using MSAL for authentication with AD B2C. It is an extension to the one presented in earlier blog post, with the addition of obtaining access tokens and calling APIs. In your terminal window, navigate to the react-app folder to run the app.


1
2
3
cd react-app
npm run start


You should see the app running as shown below.


/images/azure-ad-b2c/adb2c-api/client-app-1.png
Client app


Click on Login to sign in or sign up to the app. This sign-in/sign-up flow is the same as we had created in our earlier blog post.


/images/azure-ad-b2c/adb2c-api/client-app-sign-in-1.png
Client app sign-in / sign-up


AD B2C returns the ID token for the signed in user. This is a JSON Web Token (JWT). You can decode the content of the token on https://jwt.ms.


/images/azure-ad-b2c/adb2c-api/client-app-2.png
ID Token


Now that you are signed in to the app and possess an ID token representing the user, do you think you will be able to call the API and get a response? Let’s try it out. Click on Call API.


/images/azure-ad-b2c/adb2c-api/client-app-api-call-unauth.png
API authorization error


As you can see above, the API responded with 401 indicating that the call is unauthorized. This is not surprising because the API needs an access token for authorization and we have only an ID token at this stage.

What is the difference between ID token and Access token?
ID token represents the user’s authentication status and usually contains information about the user such as their name, email address or other profile data. It is supposed to be consumed by the client app which is authenticating the user. An access token on the other hand represents the user’s authorization to access APIs. It is supposed to be consumed by the API app and authorize / deny API access based on the scopes contained in the access token. If you are interested to explore more, this is a good reference to read.

Now, let’s get the access token for our API from AD B2C. Click on Get Access Token. Note that, for obtaining access tokens, a user needs to be signed in. In case user is not signed in when you try to obtain access token, the app will pop up a sign in page and then sign-in user and obtain access token as well.


/images/azure-ad-b2c/adb2c-api/client-app-get-access-token.png
Get Access Token


Similar to ID tokens, you can also decode the access token on https://jwt.ms. You can see that the access token we obtained contains scope (indicated by scp property) read_data

Now if we call the API, you should see response from the API as the client app sends the access token in the authorization header which the API validates.


/images/azure-ad-b2c/adb2c-api/client-app-api-call-auth.png
API response


You can also try to use the access token to access API from any other client such as Postman we tried earlier. If you do so, then use the authorization type as Bearer Token and paste the access token in the Token box. Keep in mind that the tokens are short lived which means the validity of the token is small time duration, 60 minutes by default in case of AD B2C. You need to obtain a new token every 60 minutes using something called as a refresh token. When using MSAL library in your code, this periodic refresh of the access token happens automatically in background without prompting the user to sign in every time.

As you can see, the API is now protected and any unauthorized access without the access token is rejected by the API.

Summary

In this post, we explored how we can protect our backend APIs with Azure AD B2C. If you wish to dig deeper, head to Microsoft docs.

I hope this post helped you to know one more feature of AD B2C. Please let me know your feedback in comments below. We will keep exploring more about AD B2C in upcoming posts. Stay tuned!