Role based authorization in Azure Functions with Azure AD and app roles
How to limit access to restful APIs in Azure Functions with .NET Core by assigning users to app roles in Azure Active Directory
In this previous post I discussed how to authenticate users in Azure Functions using Authentication/Authorization (formerly know as Easy Auth): https://medium.com/medialesson/protecting-azure-function-apps-with-azure-ad-authentication-authorization-fd167ce4fe33
Authenticating users in a backend API built with .NET Core and running in Azure Functions is usually just the first step to control and limit access to resources and features to certain users or groups. Now that we know who the user is we need to decide whether to allow access to a resource based on app roles.
One important drawback to consider: app roles are created individually per app registration. This means that in typical client server situations where we have a client (e.g. Angular) and a server/backend (e.g. Azure Functions) this means we need to create app roles in both app registrations and also manage user assignments for both separately. This can be mitigated by assigning app roles to security groups but this feature is limited to Azure AD premium tenants and so not available if an organization is using the Azure AD that comes with Microsoft 365.
Read more about app roles and other options for authorization here: https://docs.microsoft.com/de-de/azure/architecture/multitenant-identity/app-roles
Setup an app role
App roles are created in the app registration’s manifest and users can be assigned those roles. So let’s open our Azure Function app registration’s manifest in the Azure portal and add a role:
Note that each role needs a unique id and the value property is the value that will be included in the user’s claims.
Assign users to an app role
To assign users open the app registration by opening it via Enterprise applications instead of App registrations:
Switch to Users and groups, select Add user, search for a user and click assign:
Limit function to a role
With app roles configured and at least one users assigned it’s time to turn to Azure Functions. Let’s start with the project we created in the previous post: https://medium.com/medialesson/protecting-azure-function-apps-with-azure-ad-authentication-authorization-fd167ce4fe33
The logged in user’s assigned roles are part of the claims in the access token used for a request. So we can pull them from the ClaimsPrincipal object:
Note how we add the ClaimsPrincipal parameter to the function’s method. Each assigned role will be in a claim with the type “roles”.
Checking the user’s roles in each function method would be pretty repetitive though. A better approach is to create some form of middleware that checks the user’s roles before entering a function method. This middleware needs to be configurable so each function can decide which role it requires. To implement this requirement a possible way is to write a custom RoleAuthorizeAttribute that derives from FunctionInvocationFilterAttribute.
This attribute class needs to have a constructor parameter to allow to specify which role(s) are required for a function using the attribute.
The params keyword in the construction allows to pass in one or more role values. The method OnExecutingAsync gets called before a function method is executed when the attribute is used like this:
Important: Don’t remove the ClainsPrincipal parameter from the function as this defines which properties are passed into the arguments of the FunctionExecutionContext parameter in the attribute’s OnExecutingAsync method.
Now that the attribute is created and wired with a first function let’s implement the OnExecutingAsync method to decide whether to continue with the function executing based on the roles:
Note that there is a custom exception AuthorizationException that is being thrown when either claims are missing or the user’s roles in the claim don’t match the valid roles. The reason for throwing an exception is that I have yet to find another way of stopping the function’s method to execute and return a useful http status code result like in this case a 401.
So my approach is to a) throw a custom exception and the b) have the function’s class implement the IFunctionExceptionFilter interface. This interface allows to intercept exceptions and return http results. Like with the RoleAuthorizeAttribute we don’t want to implement this interface every time we need to use roles so let’s create a base class for all functions using roles:
To write a response this base class needs to have a IHttpContextAccessor injected in the constructor. Then we can filter for our custom AuthorizedException and return our 401 response.
Finally our function’s class needs to derive from BaseAuthorizedFunction. As we need to pass through the IHttpContextAccessor in the constructor we need to make the function’s class and method non static:
With this setup we have a reusable solution to limit execution of HttpTrigger functions (aka http apis) to not only authenticated users but also authorize based on the user’s assigned app roles. This works both in typical line of business as well as SaaS scenarios.