Events
Take the Microsoft Learn Challenge
Nov 19, 11 PM - Jan 10, 11 PM
Ignite Edition - Build skills in Microsoft Azure and earn a digital badge by January 10!
Register nowThis browser is no longer supported.
Upgrade to Microsoft Edge to take advantage of the latest features, security updates, and technical support.
Learn how to create and configure a backend App service to accept a frontend app's user credential, then exchange that credential for a downstream Azure service. This allows a user to sign in to a frontend App service, pass their credential to a backend App service, then access an Azure service with the same identity.
In this tutorial, you learn how to:
Complete the previous tutorial, Access Microsoft Graph from a secured JavaScript app as the user, before starting this tutorial but don't remove the resources at the end of the tutorial. This tutorial assumes you have the two App services and their corresponding authentication apps.
The previous tutorial used the Azure Cloud Shell as the shell for the Azure CLI. This tutorial continues that usage.
The tutorial shows how to pass the user credential provided by the frontend app to the backend app then on to an Azure service. In this tutorial, the downstream service is Microsoft Graph. The user's credential is used to get their profile from Microsoft Graph.
Authentication flow for a user to get Microsoft Graph information in this architecture:
Previous tutorial covered:
user_impersonation
.User.Read
. This is added by default to all app registrations.This tutorial extends the architecture:
az webapp up
.This tutorial doesn't:
User.Read
is added by default to all authentication apps.In the previous tutorial, when the user signed in to the frontend app, a pop-up displayed asking for user consent.
In this tutorial, in order to read user profile from Microsoft Graph, the back-end app needs to exchange the signed-in user's access token for a new access token with the required permissions for Microsoft Graph. Because the user isn't directly connected to the backend app, they can't access the consent screen interactively. You must work around this by configuring the back-end app's app registration in Microsoft Entra ID to grant admin consent. This is a setting change typically done by an Active Directory administrator.
Open the Azure portal and search for your research for the backend App Service.
Find the Settings -> Authentication section.
Select the identity provider to go to the authentication app.
In the authentication app, select Manage -> API permissions.
Select Grant admin consent for Default Directory.
In the pop-up window, select Yes to confirm the consent.
Verify the Status column says Granted for Default Directory. With this setting, the back-end app is no longer required to show a consent screen to the signed-in user and can directly request an access token. The signed-in user has access to the User.Read
scope setting because that is the default scope with which the app registration is created.
In the previous tutorial, the backend app didn't need any npm packages for authentication because the only authentication was provided by configuring the identity provider in the Azure portal. In this tutorial, the signed-in user's access token for the back-end API must be exchanged for an access token with Microsoft Graph in its scope. This exchange is completed with two libraries because this exchange doesn't use App Service authentication anymore, but Microsoft Entra ID and MSAL.js directly.
Open the Azure Cloud Shell and change into the sample directory's backend app:
cd js-e2e-web-app-easy-auth-app-to-app/backend
Install the Azure MSAL npm package:
npm install @azure/msal-node
Install the Microsoft Graph npm package:
npm install @microsoft/microsoft-graph-client
The source code to complete this step is provided for you. Use the following steps to include it.
Open the ./src/server.js
file.
Uncomment the following dependency at the top of the file:
import { getGraphProfile } from './with-graph/graph';
In the same file, uncomment the graphProfile
variable:
let graphProfile={};
In the same file, uncomment the following getGraphProfile
lines in the get-profile
route to get the profile from Microsoft Graph:
// where did the profile come from
profileFromGraph=true;
// get the profile from Microsoft Graph
graphProfile = await getGraphProfile(accessToken);
// log the profile for debugging
console.log(`profile: ${JSON.stringify(graphProfile)}`);
Save the changes: Ctrl + s.
Redeploy the backend app:
az webapp up --resource-group myAuthResourceGroup --name <back-end-app-name>
In order to change the backend API audience token for a Microsoft Graph token, the backend app needs to find the Tenant ID and use that as part of the MSAL.js configuration object. Because the backend app with configured with Microsoft as the identity provider, the Tenant ID and several other required values are already in the App service app settings.
The following code is already provided for you in the sample app. You need to understand why it's there and how it works so that you can apply this work to other apps you build that need this same functionality.
Open the ./backend/src/with-graph/auth.js
file.
Review the getTenantId()
function.
export function getTenantId() {
const openIdIssuer = process.env.WEBSITE_AUTH_OPENID_ISSUER;
const backendAppTenantId = openIdIssuer.replace(/https:\/\/sts\.windows\.net\/(.{1,36})\/v2\.0/gm, '$1');
return backendAppTenantId;
}
This function gets the current tenant ID from the WEBSITE_AUTH_OPENID_ISSUER
environment variable. The ID is parsed out of the variable with a regular expression.
Still in the ./backend/src/with-graph/auth.js
file, review the getGraphToken()
function.
Build the MSAL.js configuration object, use the MSAL configuration to create the clientCredentialAuthority. Configure the on-behalf-off request. Then use the acquireTokenOnBehalfOf to exchange the backend API access token for a Graph access token.
// ./backend/src/auth.js
// Exchange current bearerToken for Graph API token
// Env vars were set by App Service
export async function getGraphToken(backEndAccessToken) {
const config = {
// MSAL configuration
auth: {
// the backend's authentication CLIENT ID
clientId: process.env.WEBSITE_AUTH_CLIENT_ID,
// the backend's authentication CLIENT SECRET
clientSecret: process.env.MICROSOFT_PROVIDER_AUTHENTICATION_SECRET,
// OAuth 2.0 authorization endpoint (v2)
// should be: https://login.microsoftonline.com/BACKEND-TENANT-ID
authority: `https://login.microsoftonline.com/${getTenantId()}`
},
// used for debugging
system: {
loggerOptions: {
loggerCallback(loglevel, message, containsPii) {
console.log(message);
},
piiLoggingEnabled: true,
logLevel: MSAL.LogLevel.Verbose,
}
}
};
const clientCredentialAuthority = new MSAL.ConfidentialClientApplication(config);
const oboRequest = {
oboAssertion: backEndAccessToken,
// this scope must already exist on the backend authentication app registration
// and visible in resources.azure.com backend app auth config
scopes: ["https://graph.microsoft.com/.default"]
}
// This example has App service validate token in runtime
// from headers that can't be set externally
// If you aren't using App service's authentication,
// you must validate your access token yourself
// before calling this code
try {
const { accessToken } = await clientCredentialAuthority.acquireTokenOnBehalfOf(oboRequest);
return accessToken;
} catch (error) {
console.log(`getGraphToken:error.type = ${error.type} ${error.message}`);
}
}
To access Microsoft Graph as a user signed in to the frontend application, the changes include:
User.Read
.Now that the code has the correct token for Microsoft Graph, use it to create a client to Microsoft Graph then get the user's profile.
Open the ./backend/src/graph.js
In the getGraphProfile()
function, get the token, then the authenticated client from the token then get the profile.
//
import graph from "@microsoft/microsoft-graph-client";
import { getGraphToken } from "./auth.js";
// Create client from token with Graph API scope
export function getAuthenticatedClient(accessToken) {
const client = graph.Client.init({
authProvider: (done) => {
done(null, accessToken);
}
});
return client;
}
export async function getGraphProfile(accessToken) {
// exchange current backend token for token with
// graph api scope
const graphToken = await getGraphToken(accessToken);
// use graph token to get Graph client
const graphClient = getAuthenticatedClient(graphToken);
// get profile of user
const profile = await graphClient
.api('/me')
.get();
return profile;
}
Use the frontend web site in a browser. The URL is in the format of https://<front-end-app-name>.azurewebsites.net/
. You may need to refresh your token if it's expired.
Select Get user's profile
. This passes your authentication in the bearer token to the backend.
The backend end responds with the real Microsoft Graph profile for your account.
In the preceding steps, you created Azure resources in a resource group.
Delete the resource group by running the following command in the Cloud Shell. This command may take a minute to run.
az group delete --name myAuthResourceGroup
Use the authentication apps' Client ID, you previously found and made note of in the Enable authentication and authorization
sections for the backend and frontend apps.
Delete app registrations for both frontend and backend apps.
# delete app - do this for both frontend and backend client ids
az ad app delete <client-id>
This error, CompactToken parsing failed with error code: 80049217
, means the backend App service isn't authorized to return the Microsoft Graph token. This error is caused because the app registration is missing the User.Read
permission.
This error, AADSTS65001: The user or administrator has not consented to use the application with ID \<backend-authentication-id>. Send an interactive authorization request for this user and resource
, means the backend authentication app hasn't been configured for Admin consent. Because the error shows up in the log for the backend app, the frontend application can't tell the user why they didn't see their profile in the frontend app.
This tutorial demonstrates an API app authenticated to Microsoft Graph, however, the same general steps can be applied to access any Azure service on behalf of the user.
Events
Take the Microsoft Learn Challenge
Nov 19, 11 PM - Jan 10, 11 PM
Ignite Edition - Build skills in Microsoft Azure and earn a digital badge by January 10!
Register now