Next step in our Coding Azure series is to limit access to authorized users in Enterprise Applications. Here’s how.
Over the previous posts in Coding Azure, you’ve seen not only how to create a cloud-native three-tier app in Azure (a frontend with a backend consisting of a Web Service and a database) but also how to allow the backend to talk only to your application’s frontend.
ICYMI, get started with Coding Azure 1: Creating Cloud-Native Apps.
You now need to consider how you’ll control access to your frontend: What users will be allowed to access your application and (optionally) what will they be allowed to do with your frontend?
You could build user authentication into your frontend app using the identity provider of your choice, but my goal in a cloud-native application is always to leave as much to Azure as possible. So, for this post, I’m going to assume that you’ll leave authentication and the assigning of identities to users entirely to the identity provider of your choice (in my case, that’s Microsoft Entra). With that approach, all your application has to do is verify that:
Azure’s Enterprise Applications will let you control which users can access your frontend. That doesn’t mean that you have to use Enterprise Applications—Azure provides multiple ways to secure your application including Azure Front Door and Web Application Firewall. However, I like Enterprise Applications because it provides a flexible, identity-based (rather than IP-based) way to control access on an application-by-application basis.
To start using Enterprise Application, in the Azure portal, search for Enterprise Applications in the search box at the top of the page and select it from the dropdown list when it appears.
In theory, after you’ve surfed to Enterprise Applications, you’ll get a list of applications, including any App Registrations that you’ve set up (if you’ve been following along in these posts, you will have created an App Registration for your application’s frontend as part of securing your frontend to your middle tier Web Service). From that list of applications, ideally, you can click the App Registration for your frontend application and open your frontend’s Enterprise Application Overview page.
However, it’s possible that you won’t have the permission to manage your application’s Enterprise Application’s settings. If so, you’ll need to get someone who does have that permission (e.g., a Global Administrator) to assign you as the owner of the Enterprise Application. To do that, after that administrator has opened the Enterprise Application Overview page for your frontend’s App Registration:
If you’re not the person who will be managing this Enterprise Application, your work is done and the person who is responsible can take over. If you are the responsible person, you should now be able to modify the Enterprise Application settings for your frontend.
To use the Enterprise Application to give selected users access to your frontend:
By default, Enterprise Applications do not restrict access to just the selected users. To restrict access, you have to change that default setting:
In earlier posts for client-side (Coding Azure 9a: Authorizing Your Frontend Client-Side App to Access a Web Service) and server-side (Coding Azure 9b: Authorizing Your Frontend Server-Side App to Access a Web Service), I covered how to set up an App Registration for a frontend application and the code required for either type of application to claim the App Registration. With these changes, if the currently logged-in user doesn’t have one of the identities assigned in your Enterprise Application, that code will now throw an error and the user won’t be able to use the application.
In production, if you have more than a dozen users in your organization, it probably makes more sense to assign groups rather than individual users to your Enterprise Application.
There are multiple benefits to using groups, in addition to the obvious one of having fewer entities to manage. Using groups turns the responsibility for managing the membership of those groups over to your Microsoft Entra administrators (i.e., not you). Microsoft Entra administrators can also use dynamic group rules to control who is in each group rather than assigning individual users.
You may also want to discuss having your Microsoft Entra administrators include your application when designing conditional access policies to control when additional authentication checks should be applied.
If you have multiple frontends using a single middle-tier Web Service, you may also want to limit which each of those frontends is allowed to do. Each of those frontends will have a different App Registration and, as a result, each of those App Registrations could have a different set of permissions (scopes and roles) for accessing the Web Service. You can, therefore, manage what counts as an authorized activity in your frontend through the Web Service permissions that your frontend has been granted.
You might prefer to allow and disallow what your frontend is allowed to do by assigning permissions to the frontend and checking for those permissions in your frontend’s code (though, as we’ll see later in this series, Azure’s App Configuration with its ability to manage features provides a more flexible way to do meet that goal).
Managing your frontend’s functionality by managing your frontend’s permissions requires two steps:
If your frontend is a client-side (i.e. JavaScript/TypeScript) app, you can assign one or more scopes to your frontend’s App Registration that your application can check for.
To assign one or more scopes to your client-side frontend, in the Azure portal, surf to your frontend’s App Registration and:
api:<client id assigned to your App Registration when it was created>
. I just accepted that ID URL.With your frontend’s Enterprise App and App Registration configured, you can now update your frontends to claim their App Registrations and check to see if what roles or scopes have been granted.
If you request a scope that doesn’t exist on the requested App Registration, the requesting code will fail, so simply requesting a permission may be all you need to do for your frontend to be limited to only doing what it’s allowed to.
Building on the code from my earlier post Coding Azure 9a: Authorizing Your Frontend Client-Side App to Access a Web Service for claiming an App Registration in a React app, the following code implements that strategy by first using the loginPopup
method to claim the App Registration, requesting only scopes assigned to the frontend app. This code will fail if the App Registration doesn’t have both of the scopes shown here assigned in its Expose an Api section:
if (accessToken === "")
{
let respApp:AuthenticationResult = await instance.loginPopup(
{
scopes: [
"api://d118…-…-…-…-…/Products.Read",
"api://d118…-…-…-…-…/Products.Manage"
]
});
If that code succeeds, indicating that the requested permissions have been granted to the app, you can then go on to request the permissions for accessing the Web Service (two calls are required because you can’t ask for permissions in two different URL IDs in a single request). This call uses the acquireTokenSilent
method, which requires the user account that will have been retrieved by the loginPopup
method:
let respApi:any = await instance.acquireTokenSilent(
{
scopes: [
"api://abda6593-7040-4635-8441-2ae82cf0a0b6/Product.Read",
],
account: respApp.account
});
}
If that isn’t enough, you can check for which individual scopes have been provided (either in the frontend’s token or the Web Service’s token), and you can use one of several different packages for decoding the access token and extracting the scopes the token contains. The package I use is jwt-decode because it’s compatible with React 19 (and from habit).
The following code uses jwt-decode’s jwtDecode
function and JwtPayload
type to decode the frontend’s access token into a variable called payload. The resulting variable has several default properties including, for example, the identifier for the account that has been granted the permissions (probably the currently logged-in user)—that’s in the object’s jti
property.
With Entra ID, the decoded payload variable has a non-standard property called scp
that contains the scope names, without their prefixes (because scp is non-standard, you have to declare the payload property as type any). If you’ve requested multiple scopes they’re concatenated together with spaces as delimiters in the scp
property.
You can check for any specific scope by using the contains function that all string types have, like this:
let payload:any = jwtDecode<JwtPayload>( respApp.accessToken );
if ( payload.scp.contains( 'Products.Read') )
{
//…actions dependent on the Products.Read scope
}
You can still test your app from your desktop as long as the redirect setting for your app is set to your app’s local URL (e.g., http://localhost:5172). Before deploying your React app to its App Service from Visual Studio Code to test it there, don’t forget to:
npm run build
.If your frontend is a server-side (i.e., ASP.NET Core) app, you can assign one or more roles to your frontend’s App Registration that your application can check for.
To assign one or more roles to your client-side frontend, in the Azure portal, surf to your frontend’s App Registration and:
For server-side applications, your call to your token cache is already retrieving all the roles assigned in the App Registration, both for the frontend and for the Web Service it calls. To determine which roles have been granted, you can use the JwtSecurityTokenHandler
object to access individual roles (the handler is included in the Microsoft.Identity.Web NuGet package that gave you ITokenAquisition
object).
The first step in checking for roles is to pass your access token to the handler’s ReadJwtToken
method, which hands back a JwtSecurityToken
.
That JwtSecurityToken
object will have a property called Claims
that holds a collection of Claim
objects which includes any role claims granted through the frontend’s App Registration. (For example, the id of the account that owns the permissions—probably the currently logged-in user—is in the object’s id property.)
The JwtSecurityToken
object doesn’t have a convenient method for checking for roles but you can use LINQ methods to check for a Claim
object with a Type of “roles” and a Value
of the role you’re looking for. This code, for example, checks to see if the access token contains the role named Products.Read:
JwtSecurityToken decodedToken = new JwtSecurityTokenHandler()
.ReadJwtToken(accessToken);
if (decodedToken.Claims.Any(cl => cl.Type == "roles" &&
cl.Value == "Products.Read"))
{
//…actions dependent on the Products.Read role
}
There is, of course, more that you can so that only authorized users can access your frontend, as I listed at the start of this post.
In your ASP.NET Core app, you might want to separate authorization code from application code by leveraging policies.
However, at this point in your development process, you have created multiple resources and, almost certainly, will need to destroy and recreate them over the course of your development process. And, of course, you’ll need to recreate these resources (or ones very much like them) in your production environment. To do that, you’ll want leverage Azure’s Resource Management (ARM) and Bicep templates. That’s my next post.
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.