Rohan Nevrikar

Cloud Consultant @ Rapid Circle

Authorization of applications in an Azure Function

On the way to Brahmatal summit (December 2018)

Introduction

While working with Microsoft Graph, most of us have assigned application permissions to an application so that the application can fetch data from Graph APIs based on the assigned permissions.

In this article, let’s try to imagine and develop things for the other end, for the API end. By the end of this article, you’ll understand how an incoming token that is created by an application can be validated before fetching resources for the request. For an API, I’m using an HTTP-triggered Azure Function.

Setting up Azure AD Apps

We’ll create 3 AD apps. Let’s name them AppRegA, AppRegB and AppRegC.

AppRegA will be the one associated with our Azure function. To enforce authorization, we first need to define roles which can be assigned to other applications. To do so, we’ll add app roles in the manifest of AppRegA. I have named this role as DNA.Read, which is the name of the permission which other apps would see. As we want roles to be assigned to applications, we’ll set allowedMemberTypes to Application. Save the changes.

Add app roles in the manifest

Next let’s setup AppRegB. We want AppRegB to be authorized to access the API. So the only thing we need to do here is add DNA.Read permission to this app. Go to API permissions, click on Add a permission, then search for AppRegA under APIs my organization uses.

Find name of the API app

Click on AppRegA. We should now be able to see DNA.Read under application permissions. That means the role which we added in the previous step can now be assigned to applications. Let’s add the permission.

Add the application permission

In API permissions page, we can see that AppRegB now contains DNA.Read permission. Awesome! Our API should allow access to AppRegB as it now contains the required permission.

All configured permissions

We also want to test with an unauthorized application. Well, AppRegC is that application. We won’t assign any role to this application, and we’ll expect our API to not allow access to AppRegC as it is not authorized.

Setting up the Azure function

So far we have created applications for which our API will enforce authorization. Now let’s create the API itself. We’ll have a basic HTTP-triggered Azure function which will be out API. I’m using Visual Studio 2019 as my development environment. Follow this link to quickly setup the function in Visual Studio.

The first thing that the API should do is check for authorization. Only authorized requests should be allowed access to the API’s contents. So let’s add an attribute to this function, which will check for authorization. Here is what my function looks like, where RoleAuthorize is an attribute and it checks for DNA.Read role in the request:

[RoleAuthorize("DNA.Read")]
[FunctionName("Function1")]
public async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", 
    Route = null)] HttpRequest req, ILogger log)
    {
        log.LogInformation("C# HTTP trigger function processed a 
        request.");
        string name = req.Query["name"];

        string requestBody = await new 
        StreamReader(req.Body).ReadToEndAsync();
        dynamic data = JsonConvert.DeserializeObject(requestBody);
        name = name ?? data?.name;

        string responseMessage = string.IsNullOrEmpty(name)
            ? "This HTTP triggered function executed successfully. Pass a 
            name in the query string or in the request body for a 
            personalized response." : $"Hello, {name}. This HTTP triggered 
            function executed successfully.";

        return new OkObjectResult(responseMessage);
    }

Next comes implementing the logic of authorization in the attribute class. RoleAuthorizeAttribute class can be derived from FunctionInvocationFilterAttribute, which contains methods OnExecutedAsync and OnExecutingAsync, which we can override to implement our logic. We want authorization check when function starts to execute, so we’ll write our main logic inside this method.

public override async Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
{
            try
            {
                HttpRequest request = executingContext.Arguments.First().Value as HttpRequest;
                if (request.Headers.ContainsKey("authorization"))
                {
                    var authHeader = AuthenticationHeaderValue.Parse(request.Headers["authorization"]);

                    if (authHeader != null &&
                        authHeader.Scheme.ToLower() == "bearer" &&
                        !string.IsNullOrEmpty(authHeader.Parameter))
                    {
                        if (_validationParameters == null)
                        {
                            // load the tenant-specific OpenID config from Azure
                            var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(
                            $"https://login.microsoftonline.com/{tenantId}/.well-known/openid-configuration",
                            new OpenIdConnectConfigurationRetriever());

                            var config = await configManager.GetConfigurationAsync();

                            _validationParameters = new TokenValidationParameters
                            {
                                IssuerSigningKeys = config.SigningKeys, // Use signing keys retrieved from Azure
                                ValidateAudience = true,                                
                                ValidAudience = expectedAudience, // audience MUST be the app ID of the API
                                ValidateIssuer = true,                                
                                ValidIssuer = config.Issuer, // use the issuer retrieved from Azure
                                ValidateLifetime = true,

                            };
                        }

                        var tokenHandler = new JwtSecurityTokenHandler();
                        SecurityToken jwtToken;

                        var result = tokenHandler.ValidateToken(authHeader.Parameter,
                            _validationParameters, out jwtToken); // validate the token, if ValidateToken did not throw an exception, then token is valid.

                        var tokenObject = tokenHandler.ReadToken(authHeader.Parameter) as JwtSecurityToken;

                        var roles = tokenObject.Claims.Where(e => e.Type == "roles").Select(e => e.Value); //retrive roles from the token

                        bool hasRole = roles.Intersect(_validRoles).Count() > 0; //Check if the token contains roles which are specified in attribute

                        if (!hasRole)
                            throw new AuthorizationException();

                    }
                }
            }
            catch (Exception ex)
            {
                throw ex;
            }
}

The important thing in the above piece of code is token validation and roles check. We want the token to be valid, i.e. it should have valid signing keys, it’s audience should be our API’s clientID and it’s tenant should match API’s tenant. That way we’ll know that the token is meant to be for this particular API.

We’ll also have to check if the token contains the roles which are passed in the attribute. This function should only be accessible by the token which contains DNA.Read role. If token validation or role check is failed, then exception is thrown and appropriate response is returned. Else, it goes on and the function gets executed.

Demo

I have written a simple PowerShell script which uses ADAL to generate tokens for an application by accepting clientID and secret. This script will call our API with the generated token. When the API is called using AppRegB’s client credentials, we receive a successful response from the Azure function.

The API returns successful response in case of AppRegB

When the API is called using AppRegC’s client credentials, we receive a forbidden error response from the Azure function. This shows that if the application doesn’t have required permission, then it will not be authorized to consume the API.

The API returns forbidden error in case of AppRegC

Conclusion

In this article, we saw how we can enforce authorization on applications using Azure functions as an API. We also saw how app roles can be added in manifest to expose more roles for consumer apps.

If you are interested in learning about authorization of users using app roles in Azure function, check out this article. I got a lot of ideas from this amazing article while I was working on POC of application authorization.

Also, you can find the code related to this article on my GitHub.

Published by

One response to “Authorization of applications in an Azure Function”

  1. […] Rohan Nevikar – Authorization of applications in an Azure Function […]

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

Blog at WordPress.com.

%d bloggers like this: