Tutorial: Authenticate and authorize users end-to-end in Azure App Service
Azure App Service provides a highly scalable, self-patching web hosting service. In addition, App Service has built-in support for user authentication and authorization. This tutorial shows how to secure your apps with App Service authentication and authorization. It uses an Express.js with views frontend as an example. App Service authentication and authorization support all language runtimes, and you can learn how to apply it to your preferred language by following the tutorial.
Azure App Service provides a highly scalable, self-patching web hosting service using the Linux operating system. In addition, App Service has built-in support for user authentication and authorization. This tutorial shows how to secure your apps with App Service authentication and authorization. It uses an Express.js with views. App Service authentication and authorization support all language runtimes, and you can learn how to apply it to your preferred language by following the tutorial.
In the tutorial, you learn:
- Enable built-in authentication and authorization
- Secure apps against unauthenticated requests
- Use Microsoft Entra ID as the identity provider
- Access a remote app on behalf of the signed-in user
- Secure service-to-service calls with token authentication
- Use access tokens from server code
Tip
After completing this scenario, continue to the next procedure to learn how to connect to Azure services as an authenticated user. Common scenarios include accessing Azure Storage or a database as the user who has specific abilities or access to specific tables or files.
The authentication in this procedure is provided at the hosting platform layer by Azure App Service. You must deploy the frontend and backend app and configure authentication for this web app to be used successfully.
Get the user profile
The frontend app is configured to securely use the backend API. The frontend application provides a Microsoft sign-in for the user, then allows the user to get their fake profile from the backend. This tutorial uses a fake profile to simplify the steps to complete the scenario.
Before your source code is executed on the frontend, the App Service injects the authenticated accessToken
from the App Service x-ms-token-aad-access-token
header. The frontend source code then accesses and sends the accessToken to the backend server as the bearerToken
to securely access the backend API. The backend server validates the bearerToken before it's passed into your backend source code. Once your backend source code receives the bearerToken, it can be used.
In the next article in this series, the bearerToken is exchanged for a token with a scope to access the Microsoft Graph API. The Microsoft Graph API returns the user's profile information.
Prerequisites
If you don't have an Azure subscription, create an Azure free account before you begin.
Use the Bash environment in Azure Cloud Shell. For more information, see Quickstart for Bash in Azure Cloud Shell.
If you prefer to run CLI reference commands locally, install the Azure CLI. If you're running on Windows or macOS, consider running Azure CLI in a Docker container. For more information, see How to run the Azure CLI in a Docker container.
If you're using a local installation, sign in to the Azure CLI by using the az login command. To finish the authentication process, follow the steps displayed in your terminal. For other sign-in options, see Sign in with the Azure CLI.
When you're prompted, install the Azure CLI extension on first use. For more information about extensions, see Use extensions with the Azure CLI.
Run az version to find the version and dependent libraries that are installed. To upgrade to the latest version, run az upgrade.
1. Clone the sample application
In the Azure Cloud Shell, run the following command to clone the sample repository.
git clone https://github.com/Azure-Samples/js-e2e-web-app-easy-auth-app-to-app
2. Create and deploy apps
Create the resource group, web app plan, the web app and deploy in a single step.
Change into the frontend web app directory.
cd js-e2e-web-app-easy-auth-app-to-app/frontend
Create and deploy the frontend web app with az webapp up. Because web app name has to be globally unique, replace
<front-end-app-name>
with a unique name.az webapp up --resource-group myAuthResourceGroup --name <front-end-app-name> --plan myPlan --sku FREE --os-type Windows --location "West Europe" --runtime "NODE:16LTS"
Change into the backend web app directory.
cd ../backend
Deploy the backend web app to same resource group and app plan. Because web app name has to be globally unique, replace
<back-end-app-name>
with a unique set of initials or numbers.az webapp up --resource-group myAuthResourceGroup --name <back-end-app-name> --plan myPlan --os-type Windows --location "West Europe" --runtime "NODE:16LTS"
Change into the frontend web app directory.
cd frontend
Create and deploy the frontend web app with az webapp up. Because web app name has to be globally unique, replace
<front-end-app-name>
with a unique set of initials or numbers.az webapp up --resource-group myAuthResourceGroup --name <front-end-app-name> --plan myPlan --sku FREE --location "West Europe" --os-type Linux --runtime "NODE:16-lts"
Change into the backend web app directory.
cd ../backend
Deploy the backend web app to same resource group and app plan. Because web app name has to be globally unique, replace
<back-end-app-name>
with a unique set of initials or numbers.az webapp up --resource-group myAuthResourceGroup --name <back-end-app-name> --plan myPlan --sku FREE --location "West Europe" --runtime "NODE:16-lts"
3. Configure app setting
The frontend application needs to the know the URL of the backend application for API requests. Use the following Azure CLI command to configure the app setting. The URL should be in the format of https://<back-end-app-name>.azurewebsites.net
.
az webapp config appsettings set --resource-group myAuthResourceGroup --name <front-end-app-name> --settings BACKEND_URL="https://<back-end-app-name>.azurewebsites.net"
4. Frontend calls the backend
Browse to the frontend app and return the fake profile from the backend. This action validates that the frontend is successfully requesting the profile from the backend, and the backend is returning the profile.
Open the frontend web app in a browser,
https://<front-end-app-name>.azurewebsites.net
.Select the
Get user's profile
link.View the fake profile returned from the backend web app.
The
withAuthentication
value of false indicates the authentication isn't set up yet.
5. Configure authentication
In this step, you enable authentication and authorization for the two web apps. This tutorial uses Microsoft Entra ID as the identity provider.
You also configure the frontend app to:
- Grant the frontend app access to the backend app
- Configure App Service to return a usable token
- Use the token in your code.
For more information, see Configure Microsoft Entra authentication for your App Services application.
Enable authentication and authorization for backend app
In the Azure portal menu, select Resource groups or search for and select Resource groups from any page.
In Resource groups, find and select your resource group. In Overview, select your backend app.
In your backend app's left menu, select Authentication, and then select Add identity provider.
In the Add an identity provider page, select Microsoft as the Identity provider to sign in Microsoft and Microsoft Entra identities.
Accept the default settings and select Add.
The Authentication page opens. Copy the Client ID of the Microsoft Entra application to a notepad. You need this value later.
If you stop here, you have a self-contained app that's already secured by the App Service authentication and authorization. The remaining sections show you how to secure a multi-app solution by "flowing" the authenticated user from the frontend to the backend.
Enable authentication and authorization for frontend app
In the Azure portal menu, select Resource groups or search for and select Resource groups from any page.
In Resource groups, find and select your resource group. In Overview, select your frontend app's management page.
In your frontend app's left menu, select Authentication, and then select Add identity provider.
In the Add an identity provider page, select Microsoft as the Identity provider to sign in Microsoft and Microsoft Entra identities.
Accept the default settings and select Add.
The Authentication page opens. Copy the Client ID of the Microsoft Entra application to a notepad. You need this value later.
Grant frontend app access to backend
Now that you've enabled authentication and authorization to both of your apps, each of them is backed by an AD application. To complete the authentication, you need to do three things:
- Grant the frontend app access to the backend app
- Configure App Service to return a usable token
- Use the token in your code.
Tip
If you run into errors and reconfigure your app's authentication/authorization settings, the tokens in the token store may not be regenerated from the new settings. To make sure your tokens are regenerated, you need to sign out and sign back in to your app. An easy way to do it is to use your browser in private mode, and close and reopen the browser in private mode after changing the settings in your apps.
In this step, you grant the frontend app access to the backend app on the user's behalf. (Technically, you give the frontend's AD application the permissions to access the backend's AD application on the user's behalf.)
In the Authentication page for the frontend app, select your frontend app name under Identity provider. This app registration was automatically generated for you. Select API permissions in the left menu.
Select Add a permission, then select My APIs > <back-end-app-name>.
In the Request API permissions page for the backend app, select Delegated permissions and user_impersonation, then select Add permissions.
Configure App Service to return a usable access token
The frontend app now has the required permissions to access the backend app as the signed-in user. In this step, you configure App Service authentication and authorization to give you a usable access token for accessing the backend. For this step, you need the backend's client ID, which you copied from Enable authentication and authorization for backend app.
In the Cloud Shell, run the following commands on the frontend app to add the scope
parameter to the authentication setting identityProviders.azureActiveDirectory.login.loginParameters
. Replace <front-end-app-name> and <back-end-client-id>.
az extension add --name authV2
authSettings=$(az webapp auth show -g myAuthResourceGroup -n <front-end-app-name>)
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.login += {"loginParameters":["scope=openid offline_access api://<back-end-client-id>/user_impersonation"]}')
az webapp auth set --resource-group myAuthResourceGroup --name <front-end-app-name> --body "$authSettings"
The commands effectively add a loginParameters
property with additional custom scopes. Here's an explanation of the requested scopes:
openid
is requested by App Service by default already. For information, see OpenID Connect Scopes.- offline_access is included here for convenience (in case you want to refresh tokens).
api://<back-end-client-id>/user_impersonation
is an exposed API in your backend app registration. It's the scope that gives you a JWT token that includes the backend app as a token audience.
Tip
- To view the
api://<back-end-client-id>/user_impersonation
scope in the Azure portal, go to the Authentication page for the backend app, click the link under Identity provider, then click Expose an API in the left menu. - To configure the required scopes using a web interface instead, see the Microsoft steps at Refresh auth tokens.
- Some scopes require admin or user consent. This requirement causes the consent request page to be displayed when a user signs into the frontend app in the browser. To avoid this consent page, add the frontend's app registration as an authorized client application in the Expose an API page by clicking Add a client application and supplying the client ID of the frontend's app registration.
Your apps are now configured. The frontend is now ready to access the backend with a proper access token.
For information on how to configure the access token for other providers, see Refresh identity provider tokens.
6. Configure backend App Service to accept a token only from the frontend App Service
You should also configure the backend App Service to only accept a token from the frontend App Service. Not doing this may result in a "403: Forbidden error" when you pass the token from the frontend to the backend.
You can set this via the same Azure CLI process you used in the previous step.
Get the
appId
of the frontend App Service (you can get this on the "Authentication" blade of the frontend App Service).Run the following Azure CLI, substituting the
<back-end-app-name>
and<front-end-app-id>
.
authSettings=$(az webapp auth show -g myAuthResourceGroup -n <back-end-app-name>)
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.validation.defaultAuthorizationPolicy.allowedApplications += ["<front-end-app-id>"]')
az webapp auth set --resource-group myAuthResourceGroup --name <back-end-app-name> --body "$authSettings"
authSettings=$(az webapp auth show -g myAuthResourceGroup -n <back-end-app-name>)
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.validation.jwtClaimChecks += { "allowedClientApplications": ["<front-end-app-id>"]}')
az webapp auth set --resource-group myAuthResourceGroup --name <back-end-app-name> --body "$authSettings"
7. Frontend calls the authenticated backend
The frontend app needs to pass the user's authentication with the correct user_impersonation
scope to the backend. The following steps review the code provided in the sample for this functionality.
View the frontend app's source code:
Use the frontend App Service injected
x-ms-token-aad-access-token
header to programmatically get the user's accessToken.// ./src/server.js const accessToken = req.headers['x-ms-token-aad-access-token'];
Use the accessToken in the
Authentication
header as thebearerToken
value.// ./src/remoteProfile.js // Get profile from backend const response = await fetch(remoteUrl, { cache: "no-store", // no caching -- for demo purposes only method: 'GET', headers: { 'Authorization': `Bearer ${accessToken}` } }); if (response.ok) { const { profile } = await response.json(); console.log(`profile: ${profile}`); } else { // error handling }
This tutorial returns a fake profile to simplify the scenario. The next tutorial in this series demonstrates how to exchange the backend bearerToken for a new token with the scope of a downstream Azure service, such as Microsoft Graph.
7. Backend returns profile to frontend
If the request from the frontend isn't authorized, the backend App service rejects the request with a 401 HTTP error code before the request reaches your application code. When the backend code is reached (because it including an authorized token), extract the bearerToken to get the accessToken.
View the backend app's source code:
// ./src/server.js
const bearerToken = req.headers['Authorization'] || req.headers['authorization'];
if (bearerToken) {
const accessToken = bearerToken.split(' ')[1];
console.log(`backend server.js accessToken: ${!!accessToken ? 'found' : 'not found'}`);
// TODO: get profile from Graph API
// provided in next article in this series
// return await getProfileFromMicrosoftGraph(accessToken)
// return fake profile for this tutorial
return {
"displayName": "John Doe",
"withAuthentication": !!accessToken ? true : false
}
}
8. Browse to the apps
Use the frontend web site in a browser. The URL is in the format of
https://<front-end-app-name>.azurewebsites.net/
.The browser requests your authentication to the web app. Complete the authentication.
After authentication completes, the frontend application returns the home page of the app.
Select
Get user's profile
. This passes your authentication in the bearer token to the backend.The backend end responds with the fake hard-coded profile name:
John Doe
.The
withAuthentication
value of true indicates the authentication is set up yet.
9. Clean up resources
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>
Frequently asked questions
How do I test this authentication on my local development machine?
The authentication in this procedure is provided at the hosting platform layer by Azure App Service. There's no equivalent emulator. You must deploy the frontend and backend app and configuration authentication for each in order to use the authentication.
The app isn't displaying fake profile, how do I debug it?
The frontend and backend apps both have /debug
routes to help debug the authentication when this application doesn't return the fake profile. The frontend debug route provides the critical pieces to validate:
- Environment variables:
- The
BACKEND_URL
is configured correctly ashttps://<back-end-app-name>.azurewebsites.net
. Don't include that trailing forward slash or the route.
- The
- HTTP headers:
- The
x-ms-token-*
headers are injected.
- The
- Microsoft Graph profile name for signed in user is displayed.
- Frontend app's scope for the token has
user_impersonation
. If your scope doesn't include this, it could be an issue of timing. Verify your frontend app'slogin
parameters in Azure resources. Wait a few minutes for the replication of the authentication.
Did the application source code deploy correctly to each web app?
In the Azure portal for the web app, select Development Tools -> Advanced Tools, then select Go ->. This opens a new browser tab or window.
In the new browser tab, select Browse Directory -> Site wwwroot.
Verify the following are in the directory:
- package.json
- node_modules.tar.gz
- /src/index.js
Verify the package.json's
name
property is the same as the web name, eitherfrontend
orbackend
.If you changed the source code, and need to redeploy, use az webapp up from the directory that has the package.json file for that app.
Did the application start correctly
Both the web apps should return something when the home page is requested. If you can't reach /debug
on a web app, the app didn't start correctly. Review the error logs for that web app.
- In the Azure portal for the web app, select Development Tools -> Advanced Tools, then select Go ->. This opens a new browser tab or window.
- In the new browser tab, select Browse Directory -> Deployment Logs.
- Review each log to find any reported issues.
Is the frontend app able to talk to the backend app?
Because the frontend app calls the backend app from server source code, this isn't something you can see in the browser network traffic. Use the following list to determine the backend profile request success:
- The backend web app returns any errors to the frontend app if it was reached. If it wasn't reached, the frontend app reports the status code and message.
- 401: The user didn't pass authentication correctly. This can indicate the scope isn't set correctly.
- 404: The URL to the server doesn't match a route the server has
- Use the backend app's streaming logs to watch as you make the frontend request for the user's profile. There's debug information in the source code with
console.log
which helps determine where the failure happened.
What happens when the frontend token expires?
Your access token expires after some time. For information on how to refresh your access tokens without requiring users to reauthenticate with your app, see Refresh identity provider tokens.
If I have a browser-based app on the front-end app, can it talk to the back end directly?
This approach requires the server code to pass the access token to the JavaScript code running in the client browser. Because there's no way to safeguard the access token in the browser, it's not a recommended approach. Currently, the Backend-for-Frontend pattern is recommended. If applied to the example in this tutorial, the browser code on the front-end app would make API calls in an authenticated session to its server code as an intermediary, and the server code on the front-end app would in-turn make the API calls to the back-end app by using the x-ms-token-aad-access-token
header value as the bearer token. All calls from your browser code to the server code would be protected by the authenticated session already.
Next steps
What you learned:
- Enable built-in authentication and authorization
- Secure apps against unauthenticated requests
- Use Microsoft Entra ID as the identity provider
- Access a remote app on behalf of the signed-in user
- Secure service-to-service calls with token authentication
- Use access tokens from server code
Advance to the next tutorial to learn how to use this user's identity to access an Azure service.