Entra ID: Understanding Enterprise Applications

In this article, we will use an SPA to call API from an App service that will authenticate the token from the SPA and return the data. Additionally, we will create a custom scope to test with our App service

Step 1: Register a multitenant application (If you already have a multitenant app registered in your tenant, please skip this step)

  1. Go to Home > Our tenant > App registrations > New registrations to create a multitenant SPA app. We have written about this in this lab.
  2. Remember to add the Redirect URL in both platform Web and SPA. The Web Redirect URL could be changed after we deploy app service.
  3. If you have already have a single tenant app please follow these instructions to change to multi tenant:
    • From the list of applications, select the app registration you want to modify
    • In the left navigation pane, under the Manage section, click on Authentication.

    • Select the option that represents multi-tenancy. It will usually be described as Accounts in any organizational directory (Any Azure AD directory - Multitenant). This means that users from any Azure AD tenant can use the application, not just users from the tenant where the application is registered.

  4. Publisher verification:
Starting November 9th, 2020 end users will no longer be able to grant consent to newly registered multitenant apps without verified publishers.
  • Navigate to Home > Our tenant > App registrations > New registrations > Manage > Branding
  • Within the Branding page, locate the Publisher domain section and click the Add a domain link. You’ll be presented with instructions to verify your domain, which usually involves adding a DNS TXT record to your domain for verification purposes.

  • After adding the required DNS record, come back to the Azure portal then confirm the domain verification
  • Once the domain is verified, under the Branding section, you’ll find a Verified publisher field. This should display your verified domain, indicating your publisher details are validated.

  • Within the Branding page, find the Publisher display name field and enter the name that you wish to display as the publisher for your application.

  • Still within the Branding page, locate the MPN ID field (or similar, noting that exact names might vary based on updates or changes in the Azure portal). Enter your Microsoft Partner Network ID into this field. Von Draphony

5. Consuming the service
  • Open your app and use the service consuming tenant to login.
  • Verify Zeroti has been added to Enterprise App in the service consuming tenant Von DraphonyOpen, and notice that the client id is the same but the object id is different.

Step 2: Pull code to run in the local environment:

  1. Clone ReactJS SPA
    • Run git clone https://DraphonyConsulting@dev.azure.com/DraphonyConsulting/BauerAG-AzureAD-DeepDive/_git/lab-2-spa
    • Create a new .env file based on .env.example file and change environment variables. The REACT_APP_APP_SERVICE_URL can be changed later, after we deploy our app service.
    • Run npm install
    • Run npm start
  2. Clone NodeJS App. This step is to see how the NodeJS works in our local environment. When we deploy the app service, we will call api from app service url, not localhost.
    • Run git clone https://DraphonyConsulting@dev.azure.com/DraphonyConsulting/BauerAG-AzureAD-DeepDive/_git/lab-2-service-implement
    • Run npm install
    • Run npm run watch
    • Run npm run serve (open another terminal window to run)

Step 3: Implementing the features for chai-tea-scope

Let’s go into the NodeJS project and add lines of code to handle if an API has a token with the scope chai-tea.get or chai-tea.post
  1. Create a file name chai-tea.ts in src/routes and let’s do some magic things in this file
    • Import Required Libraries: Bring in the necessary modules from Express for handling web requests and from the JWT library for token operations.
      • Add import { Request, Response, Router } from "express";
      • Add import jwt, { JwtPayload } from "jsonwebtoken";
    • Initialize the Express Router: Create a new Express router to set up our endpoints. This router will be exported for use in other parts of our application.
      • Add export const chai = Router();
    • Set Initial Chai Tea Inventory: We start with 8 units of chai tea. This variable will act as our data store for this example.
      • Add let chai_tea = 8;
    • Create the GET Endpoint: Begin defining a GET endpoint that clients will access to request chai tea.
      • Add chai.get("/", (req: Request, res: Response) => {
    • Define Required Scope for GET: Identify the necessary scope to access this endpoint. Clients with tokens missing this scope will be denied access.
      • Add const required_scope = "chai-tea.get";
    • Extract the Access Token: Retrieve the JWT token, which is expected in the „Authorization“ header as „Bearer [TOKEN]“.
      • Add const accessToken = req.headers.authorization?.split(" ")[1];
    • Decode the JWT Token: Decode the JWT token to get its payload, which contains claims like scopes.
      • Add const decodedToken = jwt.decode(accessToken as string) as JwtPayload;
    • Retrieve the Scopes from the Token: Extract the scopes from the decoded token. Scopes determine what the bearer of the token is allowed to do.
      • Add const scopes = decodedToken?.scp;
    • Validate the Required Scope: Check if the client’s token has the necessary scope for this operation. If not, send an error response.
      • Add if (!scopes.split(' ').includes(required_scope)) { return res.json({ err: `'${required_scope}' scope is missing in token` }); }
    • Check Chai Tea Stock: Before serving chai tea, ensure there’s still some left.
      • Add if(chai_tea <= 0) { return res.json({ err: out of chai_tea}); }
    • Update Inventory and Respond: Serve the chai tea by reducing the stock by one and send a detailed response.
      • Add chai_tea--; res.json({ scopes, product: "chai-tea", amount: 1, remainings: chai_tea, });
    • End of GET Endpoint: This closes the callback function for our GET endpoint.
      • Add });
    • Create the POST Endpoint: Define a POST endpoint clients will use to replenish the chai tea stock.
      • Add chai.post("/", (req: Request, res: Response) => {
    • Define Required Scope for POST: Determine the necessary scope to restock the chai tea. Tokens without this scope won’t be allowed.
      • Add const required_scope = "chai-tea.post";
    • The next three lines (extracting the token, decoding it, and retrieving the scopes) follow the same logic as lines 6-8 in the GET endpoint:
      • Add const accessToken = req.headers.authorization?.split(" ")[1]; const decodedToken = jwt.decode(accessToken as string) as JwtPayload; const scopes = decodedToken?.scp;
    • Validate Scope for POST: Ensure the token contains the necessary scope to restock chai tea. If not, deny the request.
      • Add if (!scopes.split(' ').includes(required_scope)) { return res.json({ err: `'${required_scope}' scope is missing in token` }); }
    • Restock Chai Tea: If the token validation passes, increase the chai tea stock by one.
      • Add chai_tea++;
    • Send Response: Send a response detailing the stock replenishment.
      • Add res.json({ scopes, product: "chai-tea", remainings: chai_tea, });
    • End of POST Endpoint: This closes the callback function for our POST endpoint.
      • Add });
  2. Adding the new route to the server:
    • When back to app.ts and add this code to add a new route for the server:

      import { chai } from "./routes/chai-tea";

      app.use("/chai", chai);

We’ve now set up two secured endpoints: one for serving and one for restocking chai tea, integrating JWT for scope-based access control.

Step 4: Deploy App Service

  1. Create Web Application
    • We need to have NodeJS application store on Azure repository. You can use git to push code from your local (which you have cloned above) or you can just import it by our repo link.
    • Now we go to App service > Web app > Create
Von Draphony Von Draphony Select your existing subscription, resource group and name the project. In the publish section, choose code because shortly we will deploy the code from Git. Von Draphony 2. Go to Deployment Center to configure Azure Repos to get CI/CD from our NodeJS project on Azure Repository Von Draphony Von Draphony 3. Go to the App Services > Authentication tab, we will add Identity Provider so that our app service will be protected. Each time calling API from the app service, make sure to pass Authorization in Header Bearer <access-token> Von Draphony Von Draphony   4. Add cors in the CORS tab so that our ReactJS SPA can call to this app service. Enter the value of Allowed Origins as * to allow every origins. Von Draphony 5. Get back into App registrations > Authentication and add redirect URL Von Draphony

Step 5: Custom Scopes

Create custom scopes for the current app service, which is chai-tea.get and chai-tea.post

  1. Go to App registrations > <our-app-name> > Expose an API and a scope.

Von Draphony

2. Navigate to API permissions to Add permission Von Draphony
  • Go in APIs my organization uses tab and find our application
Von Draphony
  • Choose Delegated permissions,  and add permissions Von Draphony
  • Remember to navigate to Home > App services > Authentication > Edit identity provider to add Allowed token audiences: Von Draphony

Step 6: Use ReactJS SPA and call API to App Service:

  1. Login on SPA with custom scopes by our organization account: Von Draphony
2. Click Go to request app service and call to /chai endpoint (our source code checks whether chai-tea.get is existed or not and return response). Get data from app service and Post data to app service different with method to call API. Von Draphony 3. Also try changing authority to https://login.microsoftonline.com/common/v2.0 and logging in by another organization’s account, we can still get data. Von Draphony
  • Then in that organization, in the Enterprise Applications tab, our application will show up, with the same Application ID and different Object ID. Von Draphony
Now, let’s go deeper with admin consent.

Step 7: Request an Application API permission

1. Navigate to App registrations > <our-app-name> > API permissions and request an API permission. We will choose Microsoft Graph and choose Application permissions. Von Draphony2. We want to get all users from our tenant so we need to enable User.Read.All and then Add permissions Von Draphony3. Grant admin consent for the permission we’ve just added. Von Draphony

Step 8: Call Graph API from API Service

1. Here we will test on local first. Our project is running on http://localhost:3000. Replace the old thai-lan.ts file content with these code below and add environment variables into .env:
import { Request, Response, Router } from "express";

export const thai = Router();

thai.get("/",async (req: Request, res: Response) => {
  const clientId = process.env.CLIENT_ID || 'your_app_client_id'
  const clientSecret = process.env.CLIENT_SECRET || 'your_app_client_secret'
  const tenantName = process.env.TENANT_NAME || 'draphony.com'
  
  const data = new URLSearchParams();
  data.append('client_id', clientId);
  data.append('scope', 'https://graph.microsoft.com/.default');
  data.append('client_secret', clientSecret);
  data.append('grant_type', 'client_credentials');
  
  try {
    const response = await (await fetch(`https://login.microsoftonline.com/${tenantName}/oauth2/v2.0/token`,
  {
    method: 'POST',
    headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/x-www-form-urlencoded'
  },
  body: data
  })).json()
  
  console.log(response.access_token);
  const access_token: string = response.access_token
  
  const users = await (await fetch('https://graph.microsoft.com/v1.0/users',
    {
    headers: {
    'Authorization': `Bearer ${access_token}`
    }
  })).json()
  
  res.json({
    msg: "This is the response from `https://graph.microsoft.com/v1.0/users`",
    users
  });
  } catch (err) {
    console.log(err);
  }
});
  • This is to use your application information (client_id, client_secret) to get am access token without signing in as a user and then use that token and get all users.
  • Our application has already have User.Read.All role, so that it is able to get all of the users in the tenants
2. Sign in SPA just to be able to call API from API service (since API service is protected by the identity provider) Von Draphony 3. Click on GO TO REQUEST APP SERVICE and change the app service URL to http://localhost:3000/thai to test, then click GET DATA FROM APP SERVICE to use the application token and get all of the users in the tenant, which needs admin consent. Von Draphony In addition, you can edit the code yourself so you can test more cases. Hopefully, this article will help you practice and gain a deeper understanding of enterprise applications as well as custom scope.

Authors

  • Azure

Authors

  • Authors

  • Newsletter zu Aktionen

    Trage dich ein um keine Aktionen von uns zu verpassen.
    Wir senden 1-2 E-Mails pro Quartal.