If you want to protect your data in your cloud-native app, you need to secure your app’s middle tier from unauthorized access. Here’s the first step: Defining the roles and scopes that your frontend apps can use to gain authorized access to middle tier.
My goal in this post and the next is to secure a Web Service running in an Azure App Service (like the one I added to an App Service in an earlier post, Coding Azure 3: Creating an App Service for a Web Service) so that a frontend application can only perform authorized activities with the Web Service.
With this strategy, I’m protecting against my user (or someone pretending to be our user) bypassing my frontend and accessing the Web Service directly. If that were possible, the user would not be able to use the Web Service because it’s my frontend app, not my user, that has the permission to use the Web Service. (I’ll discuss controlling access to the frontend in a later post.) Presumably, the code in my frontend app won’t allow the user to do anything malicious (in a later post, I’ll discuss limiting what the user can do).
To implement securing your Web Service to your frontend, you need to create two App Registrations:
You also need to add code to both your frontend application and your Web Service to claim their respective App Registrations and:
In this post, I’m going to walk through creating the App Registration and the code to secure your Web Service. In my next two posts, I’ll look at the App Registration and code required for both a client-side JavaScript/TypeScript React front and for a server-side ASP.NET Core frontend.
An App Registration for a Web Service running in an App Service has two options for defining the permissions that frontend applications can claim, depending on the kind of frontend that will be using the Web Service:
After the App Registration is defined, it’s the responsibility of the Web Service to both claim the registration that defines these roles/scopes and then to check that the frontend app is providing them.
By the way: In real life, you may not be allowed to create App Registrations and may need to get a Global Administrator to create it. Once the Global Administrator has created an App Registration, though, you can be assigned as the App Registration’s Owner, which will let you manage the App Registration.
To create the App Registration for your Web Service and define roles or scopes that your Web Service’s code will check for before performing any action:
If your job ends with creating the App Registration and someone else (e.g., a developer) is going to be responsible for the rest of the registration’s configuration, then, in the menu on the left of the App Registration’s Overview page, drill down through Manage | Owner and assign the user that will modify the App Registration to be the App Registration’s owner.
As part of creating the Web Service’s App Registration, you need to define a URI that will be used to identify your service when the frontend claims the service’s permissions. In the App Registration’s menu on the left, select the Expose an API page. In the page that’s displayed on the right, beside the Application ID URI label, click on the Add link to open the Edit application ID URI panel on the right.
This panel will display a default ID as api:
followed by the client id that was generated for your App Registration when you created it. You can accept this suggested URI or change it to some other unique value that includes your domain name, is “URL-compliant,” doesn’t end with a slash and begins with one of: https:
, api:
, urn:
or ms-appx:
. I just accepted the default value.
Click the Save button to save your ID and close the panel.
If your Web Service will be called by a server-side frontend (e.g., an ASP.NET Core application), you can define roles for this App Registration that your code can check for when your Web Service is called by a server-side frontend.
In the App Registration’s left-side menu, drill down through the Manage node and select App Roles. In the menu at the top of the page, click on the + Create App Role menu choice to open a panel on the right.
In this panel, in the Display Name textbox, give your role a user-friendly name that reflects a business purpose for an application that will use your Web Service. (Because I was assigning a role that would allow an application to retrieve rows from the Products table in my database, I gave my role the name “Product Reader”.) In the Allowed member types list, I selected Applications because I only want applications to be allowed to call my Web Service.
In the Value textbox, enter some text (probably the display name with its spaces removed). This is the string that you will check for in your Web Service when validating the frontend application’s request (I used ProductReader
).
If your Web Service will be called by a client-side frontend (i.e., a JavaScript/TypeScript single-page app), then you need to define a scope in your Web Service’s App Registration.
In the App Registration’s left-side menu, drill down through the Manage node and select Expose an API to display a new page on the right. Click on the + Add a scope choice to open a panel on the right.
In the panel, enter:
<some resource>.<some action>.<optionally, a qualifier>
. (Because I was assigning a scope that would allow an application to retrieve rows from the Products table in my database, I used Product.Read.All
.)After completing the panel, click the Add scope button.
It’s worth taking a moment to use Azure’s tool for checking that you’ve set up your Web Service’s registration correctly.
From the menu down the left side of your App Registration, select Integration assistant to open the assistant on the right. From the dropdown list, select Web API and click the Evaluate my app registration button.
The assistant will display a list of required and optional recommendations to implement and a set of recommendations that you shouldn’t implement. Provided you have green checkmarks on all the required and shouldn’t recommendations, your App Registration should be ready to use (you can safely ignore the optional recommendations, at least for now).
Now you can add code to your Web Service to claim your Web Service’s App Registration and check for what role(s) are provided in any request to the Web Service from a frontend application. I’m going to assume that your Web Service is created using ASP.NET Core’s Web API (either with controllers or with minimal APIs).
For your Web Service to claim the App Registration you created, you’ll need to add the Microsoft.Identity.Web NuGet Package to your Web Service application. You’ll also need to ensure that your Web Service’s Program.cs file has calls to both AddAuthorization
and UseAuthorization
. This code is typical:
builder.Services.AddAuthorization();
var app = builder.Build();
app.UseAuthorization();
Next, you need to add the information needed to claim your App Registration to your Web Service’s appsettings.json file in a section called AzureAD: your Azure tenant id, the unique identifier for your App Registration (its client id), and the URL for your App Service (you can find all of those on your App Registration’s Overview page). You also need to specify the URL for the identity manager you’re using. If you’re using Entra ID, that’s “https://login.microsoftonline.com/”:
"AzureAD": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "<the URL for your Web service: <App Service Name>.azurewebsites.net",
"TenantId": "< tenant/directory id from the Overview page of the Web service’s App Registration>",
"ClientId": "<application/client id from the Overview page of the Web service’s App Registration>"
},
Here’s a typical entry:
"AzureAD": {
"Instance": "https://login.microsoftonline.com/",
"Domain": "warehousemgmtservice.azurewebsites.net",
"TenantId": "e98…-…-…-…-…",
"ClientId": "abd…-…-…-…-…"
},
Next, you need to add code to your program.cs file to claim the App Registration you set up. This code will retrieve information from your service’s appsettings.json file’s AzureAD section and tie your Web Service to the App Registration specified in the ClientId
property:
services.AddAuthentication()
.AddMicrosoftIdentityWebApi(builder.Configuration);
Now that you’ve retrieved your App Registration, you need to tie your Web Service methods to the roles or scopes you defined in your Web Service’s App Registration (i.e., these roles or scopes will, eventually, be claimed by any frontend application to your Web Service and passed to your Web Service).
If you’re using a controller to hold your Web Service methods, open your Web Service, find your controller and add the Authorize attribute to the controller class. Then, within that class, find your Web Service methods and decorate each with the Authorize attribute, passing the required role or scope you defined in your App Registration as a string:
RequiredScope
attribute.This role-based code requires the frontend application to have passed the ProductReader
role in order to execute this Get method:
[Authorize]
public class Products
{
[Authorize(Roles = "ProductReader")]
public IEnumerable<Product> Get()
{
This scope-based code expects the frontend to have passed a scope called Product.Read.All
:
[Authorize]
public class Products
{
[RequiredScope("Product.Read.All")]
public IEnumerable<Product> Get()
{
The RequiredScope
attribute also supports retrieving your expected scopes from your appsettings.json file through the RequiredScope
’s RequiredScopesConfigurationKey
property. This example pulls the required scope from a setting named AppScope
:
[Authorize]
public class Products
{
[RequiredScope(RequiredScopesConfigurationKey="AppScope"]
public IEnumerable<Product> Get()
{
The relevant section in the appsettings.json file might look like this:
"AppScope": "Product.Read.All"
If your Web Service is using minimal APIs, you need to add a call to the RequireAuthorization
method to your Web Service method. The RequireAuthorization method must be passed a lambda expression which accepts an AuthorizationPolicyBuilder
object as a parameter (I’ve called the parameter apb
in the following code).
If your frontend is a server-side application, use the AuthorizationPolicyBuilder’s RequireRole method to determine if your frontend application has passed the role your method requires. You’ll end up with code like this that requires the application to have passed the ProductReader
role:
app.MapGet("/products", (HttpRequest req) =>
{
…method code…
})
.WithName("GetAllProducts")
.RequireAuthorization(
(abp) => { abp.RequireRole("ProductReader"); }
);
If your frontend is a client-side application, use the AuthorizationPolicyBuilder’s RequireScope method to determine if your frontend application has passed a scope claim with the required scope. This code checks for a scope called Product.Read.All
:
app.MapGet("/products", (HttpRequest req) =>
{
…method code…
})
.WithName("GetAllProducts")
.RequireAuthorization(
(abp) => { abp.RequireScope("Product.Read.All"); }
);
If your Web Service is going to be accessed by both server-side and client-side apps, then you can use the RequiredScopeOrAppPermission attribute with Web API services or, with minimal APIs, the equivalent RequiredScopeOrAppPermission method. Both let you specify an array of scopes and an array of roles.
This Web API example lets the method execute when the frontend provides either a scope called MgrScope
or a role called MgrRole
:
[RequiredScopeOrAppPermission(AcceptedScope = ["MgrScope"],
AcceptedAppPermission = ["MgrRole"])]
public IEnumerable<Product> Get()
This minimal API version does the same thing:
app.MapGet("/products", (HttpRequest req) =>
{
…method code…
})
.WithName("GetAllProducts")
.RequireAuthorization(
(abp) => {
abp.RequireScopeOrAppPermission( ["MgrScope"],["MgrRole"]);
});
You can also use ASP.NET authorization policies to define a policy that includes both roles and scopes.
Another option is, rather than check for roles declaratively, to validate roles or scopes in your code using either of two methods on the HttpContext object:
Calling either method will cause your service to return a 403 (Forbidden) message if the role or scope you pass to the method hasn’t been passed from the frontend application.
Using these methods also lets you:
You can call either method directly in a Web API Web Service’s controllers like this:
HttpContext.ValidateAppRole("ProductReader");
HttpContext.VerifyUserHasAnyAcceptedScope([“Product.Read.All”])
In a minimal API, your Web Service method will have to accept the HttpContext object. After that, you can then call either method from that HttpContext object:
app.MapGet("/products", (HttpRequest req, HttpContext ctx) =>
{
…
string requiredRole = "ProductReader";
ctx.ValidateAppRole(requiredRole);
ctx.VerifyUserHasAnyAcceptedScope([“Product.Read.All”])
You can now deploy your Web Service back to its Azure App Service and call it from a client-side or server-side frontend.
The next obvious step is for your server-side and client-side applications to claim the scopes or roles they needs to access this Web Service and pass them on to the Web Service. That’s my next two posts: one for a client-side app and one for a server-side app.
Peter Vogel is both the author of the Coding Azure series and the instructor for Coding Azure in the Classroom. Peter’s company provides full-stack development from UX design through object modeling to database design. Peter holds multiple certifications in Azure administration, architecture, development and security and is a Microsoft Certified Trainer.