Telerik blogs

Your app’s TypeScript/JavaScript client-side React frontend needs to claim the scopes that will allow it to access your app’s secured backend.

In Part 8: Securing a Web Service in an Azure App Service, I covered how to create an App Registration for a Web Service running in an App Service. That App Registration defined one or more scopes that the Web Service’s code expects a client-side frontend to provide before the Web Service would execute any of the frontend’s requests.

In this post, I’m going to walk through creating an App Registration for a client-side (e.g., JavaScript/TypeScript) frontend that makes those scopes available to your frontend application, along with the code to claim and pass those scopes on to the Web Service that expects them. I use React for my client-side development, so I’m going to leverage its features in my sample code.

Creating a Frontend App Registration

To create an App Registration for your frontend, surf to the Azure Portal, and open the App Registrations page. Then:

  1. At the left end of the menu across the top of this page, click on + New Registration, and give your registration a name that refers to your frontend (I used WarehouseMgmtRegFrontEnd).
  2. In the following radio button list of choices, I’m assuming that you’re creating an app to be used internally and, as a result, want the first choice: Accounts in this organizational directory only. The other options are there to support organizations that have multiple Azure tenants, third-party vendors selling solutions to Azure users, and products accessed by random users (e.g., your customers).
  3. Click the Register button to save your App Registration.

If you’re creating this registration for someone else to modify, in the menu on the left, select the Owners choice and add the user who will manage the registration as an Owner for the App Registration.

Accepting Authentication Tokens

For your client-side frontend, you must add a URL to your App Registration where user authentication tokens can be sent (note: not authorization tokens—that will come later). These authentication tokens identify the currently logged on user to the app. To add this URL:

  1. In your App Registration’s menu on the left, expand the Manage node and select Authentication to display the Platform configurations page.
  2. On that page, click the + Add a platform choice to display Web applications panel on the right.
  3. For your client-side JavaScript/TypeScript app, pick the Single-page application option to move to the second page in the panel.
  4. On that second page, enter the URL for your frontend’s app service (e.g., for my App Service, I entered https://warehousemgmtfrontendreact.azurewebsites.net).
  5. If you also want to test your frontend without having to deploy it to your app service, you can add the URL that your frontend uses when running from your development tool. Just click on the Add URI link and enter the local URL (e.g. “http://localhost:5172”).
  6. Next, scroll down to the bottom of the panel, check off ID tokens… checkbox and click the Configure button to close the panel.

Specifying the Scopes

Now you need to select which of the scopes defined in your Web Service’s App Registration are the ones you want your client-side frontend to use (probably, all of them). For that:

  1. In the menu on the left of your App Registration, under the Manage node, select the Api Permissions choice to display the Api Permissions page on the right.
  2. At the left end of the menu across the page, select the + Add a permission choice to display the Request API Permissions panel on the right.
  3. In this panel, select the APIs my organization uses tab.
  4. In that tab, in the Apps in your directory, type the name of your Web Service’s App Registration (e.g., WarehouseMgmtServiceReg).
  5. When your Web Service’s registration appears, select it.
  6. To assign one or more of the scopes you defined in your Web Service’s App Registration, click on the Delegate permissions box to display the scopes you defined.
  7. Check the scope(s) you want your frontend application to be able to have.
  8. Click the Add permission button to close the panel and add the scope to the list of scopes in your frontend’s API permissions page.

When your list of roles refreshes, their Status column will display a warning sign and a message beginning Not granted for…. Someone (either an administrator during “configuration time” or a user at “run time”) can grant consent to those scopes being given to your frontend app.

If you grant consent now, then, at runtime, the user running the app won’t be asked to grant consent (and, if that user is an administrator, the user can grant that permission to everyone in the organization at that time). Granting consent may be something else that you’re not allowed to do and will need to get an administrator to surf to your registration to grant consent.

To grant consent now, still on the API Permissions page, at the top of the list of permissions, click the green checkmark beside the Grant admin consent for… choice and click Yes in the “are you sure” textbox that appears. All of the scopes you’ve assigned will be given consent (if you want, you can use the ellipsis——at the right end of each scope to selectively remove Admin consent).

You’re now ready to have your frontend app claim this App Registration with its scopes and then pass those scopes to your Web Service.

Troubleshooting

It’s worth taking a moment to use the provided tool to check 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 Single page app (SPA).

After making your selection in the first dropdown list, click anywhere on the page to close the list and reveal the Is this application calling APIs? option. Set the toggle beneath that to Yes and then click the Evaluate my app registration button.

The assistant will display a list of required and optional recommendations to implement plus 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).

Authorizing Your Client-side TypeScript/JavaScript Frontend

Before you can start coding your client-side you’ll need to add the MSAL for JavaScript package to your app. If you’re using React and Node.js to build your app, as I am, then you want to install these two packages:

npm i @azure/msal-browser  
npm i @azure/msal-react

Connecting to Azure and Microsoft Entra

Next, you need to instantiate a PublicClientApplication object. You want to make sure that you do this only once for your page, so, in a React app, it makes sense to do that in your app’s startup component. Depending on what scaffolding framework you used to create your app that will either be:

  • If you scaffolded your app with create-react-app, the startup file will be index.jsx (index.tsx for a TypeScript project).
  • If you scaffolded your app with Vite, the startup file will be main.jsx (main.tsx for a TypeScript project—what I used).

When you instantiate the PublicClientApplication object, you must pass it an object literal with three pieces of information:

  • The client id for your frontend’s App Registration (available from your frontend’s App Registration’s Overview page).
  • The URL for your identity provider. If you’re using Entra ID, that’s “https://login.microsoftonline.com/” followed by your tenant id (your tenant id is also available on your App Registration’s Overview page).
  • The URL you set up for your tokens to be sent to (available on your App Registration by drilling down on the menu on the left through the Manage node to the Authentication node).

To do all that, you’ll first need to import the components you’ll need at the top of your root file (main.tsx or Index.tsx):

import { MsalProvider } from '@azure/msal-react';
import { PublicClientApplication } from '@azure/msal-browser';

This code defines the object literal that holds that information and creates the PublicClientApplication from that object literal (you can call this object literal anything you want—I used initMsal):

const initMsal = {
    auth: {
     clientId:     "<your client id from the App Registration Overview page>",
     authority:    "https://login.microsoftonline.com/<your tenant id from the App Registration Overview page>",
     redirectUri:  "https://<name of your frontend’s app service>.azurewebsites.net"
                }
   };
  
const pca:PublicClientApplication = new PublicClientApplication(msalConfig);

A typical example would look like this:

const initMsal = {
    auth: {
                   clientId:     "d11…-…-…-….",
                   authority:    "https://login.microsoftonline.com/e98...-...-...-...",
                  redirectUri:  "https://warehousemgmtfrontendreact.azurewebsites.net"
                }
   };

const pca:PublicClientApplication = new PublicClientApplication(msalConfig);

If you want to test your frontend without having to deploy it to your App Service, set the redirectUri to the URL used when testing your frontend locally (e.g., “http://localhost:5172”). The easiest way to find that URL in Visual Studio Code is to use the npm run dev command in your terminal window to start your app and then copy the URL that the command displays.

You’ll need to use that PublicClientApplication in your app’s components to get the token you need to pass to your Web Service. React makes it easy to share the PublicClientApplication object with your app’s child components: Just wrap your top-level child components in an MsalProvider component, passing your PublicClientApplication object to the component’s instance property.

This code now makes the PublicClientApplication object I created in my earlier code available to a component named App (and all of the App component’s children):

  createRoot(document.getElementById('root')!).render(
      <MsalProvider instance={pca}> 
         <App />
       </MsalProvider> 
)

Retrieving the Authorization Tokens

Your next step is, in the component where you intend to call your Web Service (in my case, the App component), to retrieve your PublicClientApplication object and the information/authorization tokens for the currently logged on user.

The React NuGet packages simplify this by providing the useMsal hook, which returns a reference to the PublicClientApplication object (in a variable called instance) and an array of the currently logged-in users (that array is called accounts).

You’ll need to import it and the AuthenticationResult type at the top of your component (I’ve included some additional React components that I’ll also need later):

import { useMsal } from "@azure/msal-react";
import { AuthenticationResult } from "@azure/msal-browser";
import { useState } from "react";
import { v6 } from "uuid";

You can use the MSAL hook like this to give you access to your PublicClientApplication (in instance) and the list of logged-in users (the accounts array):

const { instance, accounts } = useMsal();

As you’ll see, I’m going to retrieve the access token in one method and use it in another method (you might, in real life, use two different components rather than just two different methods). I need a place to store that token between when it’s retrieved and when it’s used, so I create a variable and method for updating that updating that variable using React’s useState hook:

const [accessToken, setAccessToken] = useState<string>("");

After the call to useMsal, If the accounts array is empty, it indicates that the user has not yet logged in using your app. You can use the PublicClientApplication object’s loginPopup method to both log the application with the user in and to get the user’s access token.

If the user logs in successfully via the popup, the accounts array will be automatically loaded with the current user’s information (though not necessarily “immediately”—you can’t count on the array being filled until the component’s UI is displayed). If user has already logged in, the method will just load the accounts array with user’s information and the user won’t be asked to log in again (though a dialog may flash on the screen).

You can call the loginPopup method in a separate component in your application but, for this case study, I’ve just set up my App component to display multiple UIs. Specifically, my component has three UIs that let the user:

  • Log in and retrieve the access token
  • Send a request to the Web Service
  • Display the results of that request

Getting the Access Token

If the app hasn’t logged the user in yet, the accounts array won’t have any members. So I first have a method that, if the accounts array is empty, displays a UI that provides the user with a button they can use to log in (and, as a result, loads the accounts array with the user’s information and gets the access token):

if (accounts.length === 0) 
  {
    return (
        <>
          <p>
            This app is not signed in!
            <br/>
            <button onClick={() => GetAccounts()}>Log In</button>
          </p>
        </>
    );
  }

That GetAccounts function first checks to see if the access token that lets me call the Web Service has already been retrieved (if so, I can skip the rest of this method). If not, I call the loginPopup method, passing an object literal that has its scopes property set to the scope that I want (in this case, the Product.Read.All scope).

The loginPopup method returns an AuthenticationResult that holds the access token which I store to be used later:

const GetAccounts = async () => {
  if (accessToken === "")
  {
    let respAPI:AuthenticationResult = await instance.loginPopup( 
    {
        scopes: [
                 "api://abd…-…-…-…-…/Product.Read.All",
                ],
    });
    setAccessToken(respAPI.accessToken);
  }
}

Calling the Web Service

My next if-block checks to see if:

  • The accounts array is not empty (if the loginPopup method succeeded, it will load the accounts array automatically, so the array’s length indicates that the app has logged in and retrieved the current user’s information).
  • But the list of retrieved objects is empty (i.e., indicating that the Web Service hasn’t been called yet).

In this if-block, I display a UI that provides the user with a button to retrieve the objects from the Web Service:

  if (accounts.length > 0 && products.length === 0)
  {
      return (
        <>
          <p>
            This app is logged in and you can now retrieve Product objects
            <br/>
            <button onClick={() =>  callTheWebService()}>Get products</button>
          </p>
        </>
      )
  }

If you’re using the JavaScript Fetch API to call your Web Service, the method that calls the Web Service looks very much like the code from an earlier post. The only real difference is that this code adds an Authorization header that holds the accessToken as a Bearer header:

    const correlationid:string = v6();
    //replace with your Web service's URL
   const WarehouseMngmntDomain: string = "warehousemgmtservice.azurewebsites.net";
    let callResult = await fetch("https://" + WarehouseMngmntDomain + "/products",
                                { 
                                  headers: {'X-Correlation-ID': correlationid,
                                                       'Authorization': 'Bearer ' + accessToken } 
                              }
    ); 
    if (callResult.ok) { setProducts(await callResult.json()); }          
  };

You’ll need to verify that your Web Service’s App Service has its CORS settings configured to accept requests from your frontend—either when running in your frontend’s App Service or when running locally during testing.

Displaying the Results

My third and final if-block checks to see if the products array is not empty (i.e., the Web Service has been successfully called) and displays a list of retrieved Product objects:

  if (products.length > 0) 
  {   
      return (
          <>      
            <br />
            {products.map(
              (prd:Product) => {
                return(
                  <>
                      <p>
                        {/* replace with property names from your type */}
                        Product Id: {prd.productId} <br/>
                        Product Name: {prd.name} <br/>
                        Product Price: {prd.listPrice} <br/>
                      </p>
                  </>
                )
              } )
            }
            </>
          );
  }          
}

You can run this frontend from your development environment (Visual Studio or Visual Studio Code) or deploy it to its App Service.

Next Steps

There is more that you can do here to secure access between your Web Service and its frontend. In a later post, for example, I’ll integrate Web API Management into this application and restrict access to my Web Service’s App Services to only requests coming from my frontend’s App Service (with some caveats). I’ll also move all the sensitive information in my appsettings.json file into the Azure Key Vault.

You could also consider adding an Application Gateway with a Web Application Firewall to your Web Service’s App Service and use that to accept only requests from your frontend’s App Service’s outbound TCP addresses (as I did with the Azure SQL database).

But, while the backend Web Service is secured, the frontend application is not. In my next post, I’ll cover how to assign permissions to your frontend that you can check in your frontend’s code.


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.