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 a ASP.NET Core app with an Angular.js front end 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 ASP.NET Core app with an Angular.js front end 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.
It also shows you how to secure a multi-tiered app, by accessing a secured back-end API on behalf of the authenticated user, both from server code and from browser code.
These are only some of the possible authentication and authorization scenarios in App Service.
Here's a more comprehensive list of things you learn in the tutorial:
- Enable built-in authentication and authorization
- Secure apps against unauthenticated requests
- Use Azure Active Directory 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
- Use access tokens from client (browser) code
You can follow the steps in this tutorial on macOS, Linux, Windows.
If you don't have an Azure subscription, create an Azure free account before you begin.
Prerequisites
To complete this tutorial:
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.
Create local .NET Core app
In this step, you set up the local .NET Core project. You use the same project to deploy a back-end API app and a front-end web app.
Clone and run the sample application
Run the following commands to clone the sample repository and run it.
git clone https://github.com/Azure-Samples/dotnet-core-api cd dotnet-core-api dotnet run
Navigate to
http://localhost:5000
and try adding, editing, and removing todo items.To stop ASP.NET Core, press
Ctrl+C
in the terminal.Make sure the default branch is
main
.git branch -m main
Tip
The branch name change isn't required by App Service. However, since many repositories are changing their default branch to
main
, this tutorial also shows you how to deploy a repository frommain
. For more information, see Change deployment branch.
Deploy apps to Azure
In this step, you deploy the project to two App Service apps. One is the front-end app and the other is the back-end app.
Configure a deployment user
FTP and local Git can deploy to an Azure web app by using a deployment user. Once you configure your deployment user, you can use it for all your Azure deployments. Your account-level deployment username and password are different from your Azure subscription credentials.
To configure the deployment user, run the az webapp deployment user set command in Azure Cloud Shell. Replace <username> and <password> with a deployment user username and password.
- The username must be unique within Azure, and for local Git pushes, must not contain the ‘@’ symbol.
- The password must be at least eight characters long, with two of the following three elements: letters, numbers, and symbols.
az webapp deployment user set --user-name <username> --password <password>
The JSON output shows the password as null
. If you get a 'Conflict'. Details: 409
error, change the username. If you get a 'Bad Request'. Details: 400
error, use a stronger password.
Record your username and password to use to deploy your web apps.
Create Azure resources
In the Cloud Shell, run the following commands to create two Windows web apps. Replace <front-end-app-name> and <back-end-app-name> with two globally unique app names (valid characters are a-z
, 0-9
, and -
). For more information on each command, see Host a RESTful API with CORS in Azure App Service.
az group create --name myAuthResourceGroup --location "West Europe"
az appservice plan create --name myAuthAppServicePlan --resource-group myAuthResourceGroup --sku FREE
az webapp create --resource-group myAuthResourceGroup --plan myAuthAppServicePlan --name <front-end-app-name> --deployment-local-git --query deploymentLocalGitUrl
az webapp create --resource-group myAuthResourceGroup --plan myAuthAppServicePlan --name <back-end-app-name> --deployment-local-git --query deploymentLocalGitUrl
In the Cloud Shell, run the following commands to create two web apps. Replace <front-end-app-name> and <back-end-app-name> with two globally unique app names (valid characters are a-z
, 0-9
, and -
). For more information on each command, see Create a .NET Core app in Azure App Service.
az group create --name myAuthResourceGroup --location "West Europe"
az appservice plan create --name myAuthAppServicePlan --resource-group myAuthResourceGroup --sku FREE --is-linux
az webapp create --resource-group myAuthResourceGroup --plan myAuthAppServicePlan --name <front-end-app-name> --runtime "DOTNETCORE|3.1" --deployment-local-git --query deploymentLocalGitUrl
az webapp create --resource-group myAuthResourceGroup --plan myAuthAppServicePlan --name <back-end-app-name> --runtime "DOTNETCORE|3.1" --deployment-local-git --query deploymentLocalGitUrl
Note
Save the URLs of the Git remotes for your front-end app and back-end app, which are shown in the output from az webapp create
.
Push to Azure from Git
Since you're deploying the
main
branch, you need to set the default deployment branch for your two App Service apps tomain
(see Change deployment branch). In the Cloud Shell, set theDEPLOYMENT_BRANCH
app setting with theaz webapp config appsettings set
command.az webapp config appsettings set --name <front-end-app-name> --resource-group myAuthResourceGroup --settings DEPLOYMENT_BRANCH=main az webapp config appsettings set --name <back-end-app-name> --resource-group myAuthResourceGroup --settings DEPLOYMENT_BRANCH=main
Back in the local terminal window, run the following Git commands to deploy to the back-end app. Replace <deploymentLocalGitUrl-of-back-end-app> with the URL of the Git remote that you saved from Create Azure resources. When prompted for credentials by Git Credential Manager, make sure that you enter your deployment credentials, not the credentials you use to sign in to the Azure portal.
git remote add backend <deploymentLocalGitUrl-of-back-end-app> git push backend main
In the local terminal window, run the following Git commands to deploy the same code to the front-end app. Replace <deploymentLocalGitUrl-of-front-end-app> with the URL of the Git remote that you saved from Create Azure resources.
git remote add frontend <deploymentLocalGitUrl-of-front-end-app> git push frontend main
Browse to the apps
Navigate to the following URLs in a browser and see the two apps working.
http://<back-end-app-name>.azurewebsites.net
http://<front-end-app-name>.azurewebsites.net
Note
If your app restarts, you may have noticed that new data has been erased. This behavior by design because the sample ASP.NET Core app uses an in-memory database.
Call back-end API from front end
In this step, you point the front-end app's server code to access the back-end API. Later, you enable authenticated access from the front end to the back end.
Modify front-end code
In the local repository, open Controllers/TodoController.cs. At the beginning of the
TodoController
class, add the following lines and replace <back-end-app-name> with the name of your back-end app:private static readonly HttpClient _client = new HttpClient(); private static readonly string _remoteUrl = "https://<back-end-app-name>.azurewebsites.net";
Find the method that's decorated with
[HttpGet]
and replace the code inside the curly braces with:var data = await _client.GetStringAsync($"{_remoteUrl}/api/Todo"); return JsonConvert.DeserializeObject<List<TodoItem>>(data);
The first line makes a
GET /api/Todo
call to the back-end API app.Next, find the method that's decorated with
[HttpGet("{id}")]
and replace the code inside the curly braces with:var data = await _client.GetStringAsync($"{_remoteUrl}/api/Todo/{id}"); return Content(data, "application/json");
The first line makes a
GET /api/Todo/{id}
call to the back-end API app.Next, find the method that's decorated with
[HttpPost]
and replace the code inside the curly braces with:var response = await _client.PostAsJsonAsync($"{_remoteUrl}/api/Todo", todoItem); var data = await response.Content.ReadAsStringAsync(); return Content(data, "application/json");
The first line makes a
POST /api/Todo
call to the back-end API app.Next, find the method that's decorated with
[HttpPut("{id}")]
and replace the code inside the curly braces with:var res = await _client.PutAsJsonAsync($"{_remoteUrl}/api/Todo/{id}", todoItem); return new NoContentResult();
The first line makes a
PUT /api/Todo/{id}
call to the back-end API app.Next, find the method that's decorated with
[HttpDelete("{id}")]
and replace the code inside the curly braces with:var res = await _client.DeleteAsync($"{_remoteUrl}/api/Todo/{id}"); return new NoContentResult();
The first line makes a
DELETE /api/Todo/{id}
call to the back-end API app.Save all your changes. In the local terminal window, deploy your changes to the front-end app with the following Git commands:
git add . git commit -m "call back-end API" git push frontend main
Check your changes
Navigate to
http://<front-end-app-name>.azurewebsites.net
and add a few items, such asfrom front end 1
andfrom front end 2
.Navigate to
http://<back-end-app-name>.azurewebsites.net
to see the items added from the front-end app. Also, add a few items, such asfrom back end 1
andfrom back end 2
, then refresh the front-end app to see if it reflects the changes.
Configure auth
In this step, you enable authentication and authorization for the two apps. You also configure the front-end app to generate an access token that you can use to make authenticated calls to the back-end app.
You use Azure Active Directory as the identity provider. For more information, see Configure Azure Active Directory authentication for your App Services application.
Enable authentication and authorization for back-end 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 back-end app's management page.
In your back-end app's left menu, select Authentication, and then click Add identity provider.
In the Add an identity provider page, select Microsoft as the Identity provider to sign in Microsoft and Azure AD identities.
Accept the default settings and click Add.
The Authentication page opens. Copy the Client ID of the Azure AD 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 front end to the back end.
Enable authentication and authorization for front-end app
Follow the same steps for the front-end app, but skip the last step. You don't need the client ID for the front-end app. However, stay on the Authentication page for the front-end app because you'll use it in the next step.
If you like, navigate to http://<front-end-app-name>.azurewebsites.net
. It should now direct you to a secured sign-in page. After you sign in, you still can't access the data from the back-end app, because the back-end app now requires Azure Active Directory sign-in from the front-end app. You need to do three things:
- Grant the front end access to the back end
- 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.
Grant front-end app access to back end
Now that you've enabled authentication and authorization to both of your apps, each of them is backed by an AD application. In this step, you give the front-end app permissions to access the back end on the user's behalf. (Technically, you give the front end's AD application the permissions to access the back end's AD application on the user's behalf.)
In the Authentication page for the front-end app, select your front-end 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 back-end app, select Delegated permissions and user_impersonation, then select Add permissions.
Configure App Service to return a usable access token
The front-end app now has the required permissions to access the back-end 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 back end. For this step, you need the back end's client ID, which you copied from Enable authentication and authorization for back-end app.
In the Cloud Shell, run the following commands on the front-end app to add the scope
parameter to the authentication setting identityProviders.azureActiveDirectory.login.loginParameters
. Replace <front-end-app-name> and <back-end-client-id>.
authSettings=$(az webapp auth show -g myAuthResourceGroup -n <front-end-app-name>)
authSettings=$(echo "$authSettings" | jq '.properties' | jq '.identityProviders.azureActiveDirectory.login += {"loginParameters":["scope=openid profile email 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 adds a loginParameters
property with additional custom scopes. Here's an explanation of the requested scopes:
openid
,profile
, andemail
are requested by App Service by default already. For information, see OpenID Connect Scopes.api://<back-end-client-id>/user_impersonation
is an exposed API in your back-end app registration. It's the scope that gives you a JWT token that includes the back end app as a token audience.- offline_access is included here for convenience (in case you want to refresh tokens).
Tip
- To view the
api://<back-end-client-id>/user_impersonation
scope in the Azure portal, go to the Authentication page for the back-end 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 front-end app in the browser. To avoid this consent page, add the front end'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 front end's app registration.
Note
For Linux apps, There's a temporary requirement to configure a versioning setting for the back-end app registration. In the Cloud Shell, configure it with the following commands. Be sure to replace <back-end-client-id> with your back end's client ID.
id=$(az ad app show --id <back-end-client-id> --query id --output tsv)
az rest --method PATCH --url https://graph.microsoft.com/v1.0/applications/$id --body "{'api':{'requestedAccessTokenVersion':2}}"
Your apps are now configured. The front end is now ready to access the back end with a proper access token.
For information on how to configure the access token for other providers, see Refresh identity provider tokens.
Call API securely from server code
In this step, you enable your previously modified server code to make authenticated calls to the back-end API.
Your front-end app now has the required permission and also adds the back end's client ID to the login parameters. Therefore, it can obtain an access token for authentication with the back-end app. App Service supplies this token to your server code by injecting a X-MS-TOKEN-AAD-ACCESS-TOKEN
header to each authenticated request (see Retrieve tokens in app code).
Note
These headers are injected for all supported languages. You access them using the standard pattern for each respective language.
In the local repository, open Controllers/TodoController.cs again. Under the
TodoController(TodoContext context)
constructor, add the following code:public override void OnActionExecuting(ActionExecutingContext context) { base.OnActionExecuting(context); _client.DefaultRequestHeaders.Accept.Clear(); _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Request.Headers["X-MS-TOKEN-AAD-ACCESS-TOKEN"]); }
This code adds the standard HTTP header
Authorization: Bearer <access-token>
to all remote API calls. In the ASP.NET Core MVC request execution pipeline,OnActionExecuting
executes just before the respective action does, so each of your outgoing API call now presents the access token.Save all your changes. In the local terminal window, deploy your changes to the front-end app with the following Git commands:
git add . git commit -m "add authorization header for server code" git push frontend main
Sign in to
https://<front-end-app-name>.azurewebsites.net
again. At the user data usage agreement page, click Accept.You should now be able to create, read, update, and delete data from the back-end app as before. The only difference now is that both apps are now secured by App Service authentication and authorization, including the service-to-service calls.
Congratulations! Your server code is now accessing the back-end data on behalf of the authenticated user.
Call API securely from browser code
In this step, you point the front-end Angular.js app to the back-end API. This way, you learn how to retrieve the access token and make API calls to the back-end app with it.
While the server code has access to request headers, client code can access GET /.auth/me
to get the same access tokens (see Retrieve tokens in app code).
Tip
This section uses the standard HTTP methods to demonstrate the secure HTTP calls. However, you can use Microsoft Authentication Library for JavaScript to help simplify the Angular.js application pattern.
Configure CORS
In the Cloud Shell, enable CORS to your client's URL by using the az webapp cors add
command. Replace the <back-end-app-name> and <front-end-app-name> placeholders.
az webapp cors add --resource-group myAuthResourceGroup --name <back-end-app-name> --allowed-origins 'https://<front-end-app-name>.azurewebsites.net'
This step is not related to authentication and authorization. However, you need it so that your browser allows the cross-domain API calls from your Angular.js app. For more information, see Add CORS functionality.
Point Angular.js app to back-end API
In the local repository, open wwwroot/index.html.
In Line 51, set the
apiEndpoint
variable to the HTTPS URL of your back-end app (https://<back-end-app-name>.azurewebsites.net
). Replace <back-end-app-name> with your app name in App Service.In the local repository, open wwwroot/app/scripts/todoListSvc.js and see that
apiEndpoint
is prepended to all the API calls. Your Angular.js app is now calling the back-end APIs.
Add access token to API calls
In wwwroot/app/scripts/todoListSvc.js, above the list of API calls (above the line
getItems : function(){
), add the following function to the list:setAuth: function (token) { $http.defaults.headers.common['Authorization'] = 'Bearer ' + token; },
This function is called to set the default
Authorization
header with the access token. You call it in the next step.In the local repository, open wwwroot/app/scripts/app.js and find the following code:
$routeProvider.when("/Home", { controller: "todoListCtrl", templateUrl: "/App/Views/TodoList.html", }).otherwise({ redirectTo: "/Home" });
Replace the entire code block with the following code:
$routeProvider.when("/Home", { controller: "todoListCtrl", templateUrl: "/App/Views/TodoList.html", resolve: { token: ['$http', 'todoListSvc', function ($http, todoListSvc) { return $http.get('/.auth/me').then(function (response) { todoListSvc.setAuth(response.data[0].access_token); return response.data[0].access_token; }); }] }, }).otherwise({ redirectTo: "/Home" });
The new change adds the
resolve
mapping that calls/.auth/me
and sets the access token. It makes sure you have the access token before instantiating thetodoListCtrl
controller. That way all API calls by the controller includes the token.
Deploy updates and test
Save all your changes. In the local terminal window, deploy your changes to the front-end app with the following Git commands:
git add . git commit -m "add authorization header for Angular" git push frontend main
Navigate to
https://<front-end-app-name>.azurewebsites.net
again. You should now be able to create, read, update, and delete data from the back-end app, directly in the Angular.js app.
Congratulations! Your client code is now accessing the back-end data on behalf of the authenticated user.
When access tokens expire
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.
Clean up resources
In the preceding steps, you created Azure resources in a resource group. If you don't expect to need these resources in the future, delete the resource group by running the following command in the Cloud Shell:
az group delete --name myAuthResourceGroup
This command may take a minute to run.
Next steps
What you learned:
- Enable built-in authentication and authorization
- Secure apps against unauthenticated requests
- Use Azure Active Directory 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
- Use access tokens from client (browser) code
Advance to the next tutorial to learn how to map a custom DNS name to your app.
Feedback
Submit and view feedback for