
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.

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.

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.

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.

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.

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.

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.
Leave a Reply