Introduction
It’s always a best practice to secure the APIs (or any other resource) once deployed into the cloud.
In here let’s build a set of APIs which will be deployed to Azure and, make them accessible to each other securely.
Scenario which we’ll be designing
A popular API design pattern is backend for front end (BFF). The design pattern is there to define a service (BFF) which will communicate with one or more services to provide a response, rather than allowing the clients to call multiple services. This will be highly beneficial to the client because otherwise it might need to maintain different settings to call each service and how and which endpoints to deal with. Also from a security perspective what if one or more of these APIs should not be made accessible to the clients directly at all? So the BFF will abstract all these services and will provide a single point of entry to the client.
In this scenario, we would not like to expose our internal APIs ( Orders API
and Products API
) but only to be accessible through a BFF.
Apps and roles in Azure Active Directory (AAD)
Since we have three different APIs (BFF, Orders and Products) we need to create three different APP registrations in AAD. After the registrations are done we can independently create roles defined individually for the respective apps.
- Let’s first register an app to represent the BFF
In our case we only need one role to be defined in the BFF
- Search for a specific order by id
There are two ways which you can create roles. Using the App Roles UI
(preview at the time of writing) and App manifest editor
. Let’s use the APP roles UI to define the roles. You can read more on this from here.
Note that we want the BFF to be accessible by both Users
and Services
Then set the accessTokenAcceptedVersion
to 2
because we would like to use OAuth V2 tokens for more fine grained security and control.
Once done you’ll be able to see the roles for the BFF
. Notice that the Allowed member types
has been set to both Users/Groups, Applications
.
Setting application IDs
Let’s try to avoid creating client ids and secrets in the first place. When we have client ids and secrets to maintain it creates an extra burden of maintaining them so let’s avoid them whenever we can.
So as the first step towards it we will need to setup Application ID URI
for all the apps which we created. You can do them as shown below.
Make sure you provide a meaningful name for the application ids.
Getting a token to access the BFF securely
Now let’s try to get a token to access the BFF securely. In here I am using Azure CLI
az account get-access-token --resource api://app.secure.sales.bff.api
But once executed we will be getting the below error,
So what’s wrong here? We are logging in to a client application (in this case Azure CLI) and through that to get a token to the BFF
. Remember that BFF
is supposed to be accessible for users. So what’s the problem here? The problem in here is if the app needs to be accessible to users the app must have one or more scopes and a scope must be associated with the specific client.
So as you can see from the above error message it mentions that the application (Azure CLI) with id “04b07795–8ddb-461a-bbee-02f9e1bf7b46" does not have rights to get a token. So let’s create a default scope called access.bff
and assign it to the AZ CLI application.
Then add the client application as shown below,
Now let’s try to get a token again for the user. Use the same AZ CLI command to get a token for BFF.
This time it’s a success! But let’s see what is inside the token. Browse to jwt.ms
or jwt.io
and copy paste the token to see what’s inside.
As you can see the token contains the correct audience for the BFF
and the scope. But as you can see there are no roles
assigned to this user and, that’s because we haven’t assigned any to the user.
User groups and users
Rather than assigning roles to individual users it makes sense to create a user group and add the users to the group. Then assigning the user group the required permissions. But depending on your Azure subscription you might not have rights to create user groups
(sadly like me 😞). But most likely in an enterprise setup you will be able to do so.
So said that, I am going to assign roles to my user to access the BFF. Go to the Enterprise applications
in AAD and add the user as shown below,
Once done, you’ll see that the user now have the required role to access the BFF.
Now let’s get an access token to the user and, inspect what’s inside there now.
As you can see now, the user have a role, but also the scope from the client application (AZ CLI) which it used to get the token for the BFF
.
Building and securing the BFF API
- Create an ASP.NET Core Web API project and add a controller called
SalesController
as shown below
As you can see the action method to get orders have been made secure using the attribute Authorize
but also making it specific to the role sales.bff.orders.search
. Meaning this action method will be authorized
to be accessible through a sales.bff.orders.search
role only.
But this is authorization, how can we make sure the authentication and the token sent are valid? For that we use the latest MSAL
library from Microsoft.
Install the nuget package microsoft.identity.web
and then add the below code in the Startup.cs
You will need to setup the configuration inside the appsettings.json
file as shown below. Please read more information about this in the microsoft documentation here.
In here the Audience
and the ClientId
are set to the ClientId
of the BFF app registration as shown below. Because we would like to validate the token for the BFF not for something else.
In the Configure
method inside the Startup.cs
register the middleware to authenticate.
Now get a token and access the web API. As you can see you’ll be able to securely access the BFF endpoint now.
Deploy BFF and verify it can be accessed securely
It would be great to create separate deployment pipelines for these APIs. But to keep things focused on the security aspects let’s deploy through RIDER
or Visual Studio
to Azure.
Once deployed check if you can access the BFF API
securely.
So far we have been able to create an AAD app which represents the BFF and its respective roles. We also deployed the BFF API to Azure. Also we have been able to obtain an access token to a user to access the BFF securely.
Azure active directory setup for orders and products
As explained earlier the BFF
needs to access both Orders
and Produts
APIs securely to provide a response to the callers.
First let’s create the respective roles for Sales
and Products
APIs.
Sales setup
- Create the role
Products Setup
- Create the role
Please make sure that you have set the accessTokenAcceptedVersion
to 2 for all the app roles in the manifest for both Orders
and Products
setup and select Applications
for the Allowed member types
.
Let’s build and deploy Orders and Products APIs
Now as we did for the BFF let’s build and deploy the Sales
and Products
APIs.
The action methods in both Orders
and in Products
API are very simple.
- Orders API
The action method is secured through the role orders.search
which we defined when we did the app registration and, below is the configuration which you’ll need to setup to validate the token sent to it.
- Products API
The action method is secured through the role products.search
. Also the configuration to validate the token is as shown below,
Once deployed you will not be able to access the Orders
or the Products
APIs since they are secured and are accessible only through applications.
Enabling BFF to access Orders and Products API through managed identity
In the recent past we had to create client id
and client secret
to access Azure services securely. But with the introduction of the awesome managed identity
that is no longer the case.
Let’s assign BFF the required roles to access the orders
and products
APIs.
Browse to the BFF
API in your Azure resource group and set the managed identity as shown below. Note that I have created a System assigned
managed identity here, but feel free to create a user assigned
identity if you would like but the concepts will be the same. This can be easily automated through ARM templates in your deployment pipeline.
This will result in an Enterprise application
creation in your AAD and you will not be able to see any permissions assigned there yet.
Do the same for the Orders
and Products
APIs.
Assigning the role to BFF to access the Sales API securely
Let’s use powershell to do this easily.
- Connect to Azure
Connect-AzureAD -TenantId [your tenant id]
- Get the client object id
$clientObjectId = "b3aa2988-a695-45f1-ad4b-6da6e3dec4b4";
- Now we’ll need the
object id
in theEnterprise applications
$enterpriseObjectId = "ee942677-0ed1-4971-b934-9571c2bd7ee2";
- The role which we are going to assign,
$appRoleName = "orders.search";
- Get the client using the
object id
# Get the client api
$client = Get-AzureADServicePrincipal -ObjectId $([GUID]$clientObjectId);
- Get the target application
# Get the resource
$resource = Get-AzureADServicePrincipal -ObjectId $([GUID]$enterpriseObjectId)
- Get the app role information,
# Get the app role information
$appRole = $resource.AppRoles | Where-Object { $_.DisplayName -eq $appRoleName }
- Now assign the app role to the
BFF API
to access theOrders API
# Assign the approle
New-AzureADServiceAppRoleAssignment -ObjectId $client.ObjectId -Id $appRole.Id -PrincipalId $client.ObjectId -ResourceId $resource.ObjectId
Once done you’ll be able to see that now the BFF
has been assigned the orders.search
role to access the Orders API
.
Now let’s see if we can access the Orders API
from the BFF API
. Let’s do some coding!
Accessing the Orders API securely from BFF API
- Install the
microsoft.extensions.http
package. Let’s create a typed HTTP client to access theOrders API
securely. - Create a configuration entry to access the
orders api
- Create a configuration class to map these
- Create a typed HTTP client class
Notice inside the GetAccessTokenAsync
method we are using the ManagedIdentityCredential
. You can use the DefaultAzureCredential
class as well, which will try to get the access token from different sources. But in here we don’t want that and, also we want to specifically access through managed identity.
- Inject the service to the controller and modify the action method.
For us to understand what’s the token looks like when obtained through managed identity I am returning the token as well as part of the response. (This is purely for demonstration purposes! 😆)
- Deploy the BFF and access the endpoint using an access token,
If you inspect the token you’ll see that it has the value orders.search
under roles
Accessing the Products API securely from BFF API
Please follow the same steps to assign the role products.search
to the BFF
and do the necessary code changes to access the products API as we did to access the orders API as well.
To demonstrate the different tokens obtained and to access the Products API
changed the action method in BFF
to return both tokens and the data from the two APIs.
Once deployed you’ll be able to access both the Orders API
and the Products APIs
securely from the BFF API
.
After analyzing the different tokens obtained by the BFF
to access the Orders API
and the Products API
you’ll be able to see that they have the respective roles
assigned in the tokens.