Telerik blogs

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:

  • Only authorized identities can call your frontend
  • Any particular identity can only perform authorized tasks (this, it turns out, is optional)

Finding and Configuring the Enterprise Application

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:

  1. From the menu on the left of the selected registration, drill down through the Manage node and select the Owners node to display a page on the right.
  2. In that page, from the menu at the top, click on the + Add choice to open the Select Owners panel on the right.
  3. From the list of identities in that panel, select the person who will manage the Enterprise Application and put a checkmark by their name.
  4. At the bottom of the panel, click the Select button.

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.

Assign Authorized Users

To use the Enterprise Application to give selected users access to your frontend:

  1. On the Enterprise Application’s Overview page, expand the Manage node and click on the Users and groups choice to get a list of all the users and groups currently assigned to your app (at this point, probably none).
  2. On the resulting Users and groups page, click the + Add user/group menu choice to open the Add Assignment page.
  3. On the left side of the page, click Users and groups to display a list of all the users and groups in your tenant.
  4. At the bottom of the panel, click the Select button to return to the Add Assignment page and save your selections.

By default, Enterprise Applications do not restrict access to just the selected users. To restrict access, you have to change that default setting:

  1. In the Enterprise Application’s menu on the left, under the Manage node, select Properties to open a new page on the right.
  2. Find the Assignment required? toggle and set it to Yes to restrict access to only those authenticated users you’ve assigned to the app.
  3. At the top of the screen, click the Save button.

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.

Authentication Considerations

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.

Specifying Authorized Activities in Your Frontend

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:

  • Defining a role or scope in your frontend’s App Registration
  • Having your frontend application check for the existence of those roles or permissions before accessing the Web Service

Specifying Authorized Actions in Client-Side (JavaScript/TypeScript) Frontends

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.

Assigning Scopes to a Client-Side Frontend

To assign one or more scopes to your client-side frontend, in the Azure portal, surf to your frontend’s App Registration and:

  1. In the menu on the left, expand the Manage node and click on the Expose an API choice to open a new page on the right.
  2. Click on the + Add a scope choice to open a panel on the right.
  3. If this is your first scope, you’ll be asked to approve an Application ID URL in the form api:<client id assigned to your App Registration when it was created>. I just accepted that ID URL.
  4. In the new panel:
    • Give your scope a name (the convention is Resource.Action.OptionalQualifier). I created a scope that I’ll check for when retrieving product information so I used the name Products.Read.All.
    • The consent option isn’t relevant in this scenario and can be left at its default.
    • You can also put whatever text you want in the subsequent textboxes (I used Read all products for all of the textboxes).
    • Click the Add scope button to add your scope to the App Registration.

Checking for Scopes in Client-Side Frontends

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
        });
      }

Parsing the Client-Side Token

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:

  1. Set the redirect option back to your App Service’s URL (e.g., something that end azurewebsites.net).
  2. Do an npm run build.

Specifying Authorized Actions in Server-Side (ASP.NET Core) Frontends

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.

Assigning Roles to a Server-Side Frontend

To assign one or more roles to your client-side frontend, in the Azure portal, surf to your frontend’s App Registration and:

  1. In the menu on the left, expand the Manage node and click on the Expose an App Roles choice to open a new page on the right.
  2. Click on the + Create app role choice to open a panel on the right.
  3. In the panel:
    • Give your role a name that will be displayed in the Azure Portal UI. (Since I was going to use this role to give permission to read product information, I gave it the name Product Reader.)
    • Since the only thing that should be able to claim this role is your frontend application, in the following radio buttons, select Applications.
    • Give your role a name (the convention is Resource.Action.OptionalQualifier). I created a scope that I’ll check for when retrieving product information so I used Products.Read.
    • Provide a description (I used Read all product information).
    • Click the Apply to save your role.

Checking for Roles in a Server-Side Frontend

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
}

Next Steps

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
About the Author

Peter Vogel

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.

Related Posts

Comments

Comments are disabled in preview mode.