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.

Simple authentication and authorization

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.

Advanced authentication and authorization

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:

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

  1. 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
    
  2. Navigate to http://localhost:5000 and try adding, editing, and removing todo items.

    ASP.NET Core API running locally

  3. To stop ASP.NET Core, press Ctrl+C in the terminal.

  4. 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 from main. 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

  1. Since you're deploying the main branch, you need to set the default deployment branch for your two App Service apps to main (see Change deployment branch). In the Cloud Shell, set the DEPLOYMENT_BRANCH app setting with the az 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
    
  2. 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
    
  3. 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

Screenshot of an Azure App Service REST API Sample in a browser window, which shows a To do list app.

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

  1. 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";
    
  2. 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.

  3. 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.

  4. 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.

  5. 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.

  6. 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.

  7. 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

  1. Navigate to http://<front-end-app-name>.azurewebsites.net and add a few items, such as from front end 1 and from front end 2.

  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 as from back end 1 and from back end 2, then refresh the front-end app to see if it reflects the changes.

    Screenshot of an Azure App Service REST API Sample in a browser window, which shows a To do list app with items added from the front-end app.

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

  1. In the Azure portal menu, select Resource groups or search for and select Resource groups from any page.

  2. In Resource groups, find and select your resource group. In Overview, select your back-end app's management page.

    Screenshot of the Resource groups window, showing the Overview for an example resource group and a back-end app's management page selected.

  3. In your back-end app's left menu, select Authentication, and then click Add identity provider.

  4. In the Add an identity provider page, select Microsoft as the Identity provider to sign in Microsoft and Azure AD identities.

  5. Accept the default settings and click Add.

    Screenshot of the back-end app's left menu showing Authentication/Authorization selected and settings selected in the right menu.

  6. The Authentication page opens. Copy the Client ID of the Azure AD application to a notepad. You need this value later.

    Screenshot of the Azure Active Directory Settings window showing the Azure AD App, and the Azure AD Applications window showing the Client ID to copy.

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.)

  1. 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.

  2. Select Add a permission, then select My APIs > <back-end-app-name>.

  3. In the Request API permissions page for the back-end app, select Delegated permissions and user_impersonation, then select Add permissions.

    Screenshot of the Request API permissions page showing Delegated permissions, user_impersonation, and the Add permission button selected.

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, and email 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.

  1. 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.

  2. 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
    
  3. 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

  1. In the local repository, open wwwroot/index.html.

  2. 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.

  3. 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

  1. 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.

  2. 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" });
    
  3. 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 the todoListCtrl controller. That way all API calls by the controller includes the token.

Deploy updates and test

  1. 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
    
  2. 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.