Azure Functions Middleware - Part 3 (Authorization Middleware)

There are two other part in this series Part 1 Part 2. First part contains basic information and second part contains authentication middleware related information.

This part contains information related to authorization middleware. In Part 2, after token validation context.Items contains information for UserRole based on that logic is being performed but it can be anything.

image.png

First of all, let's create FunctionAuthorizeAttribute and this will look like following.

 /// <summary>
    /// This attribute is used to decorate Function with authorize attribute.
    /// It contains one or more roles.
    /// </summary>
    [AttributeUsage(AttributeTargets.Method)]
    public class FunctionAuthorizeAttribute : Attribute
    {
        public FunctionAuthorizeAttribute(params string[] roles)
        {
            Roles = roles;
        }

        public IEnumerable<string> Roles { get; }
    }

Function will decorate with FunctionAuthorize attribute.

public class SimpleSecureFunctions
    {
        [Function("GetData")]
        [FunctionAuthorize(new[] { "Admin" })]
        public  HttpResponseData GetData([HttpTrigger(AuthorizationLevel.Anonymous, "get", "post")] HttpRequestData req,
            FunctionContext executionContext)
        {
            var logger = executionContext.GetLogger("Function1");
            logger.LogInformation("C# HTTP trigger function processed a request.");

            var response = req.CreateResponse(HttpStatusCode.OK);
            response.Headers.Add("Content-Type", "text/plain; charset=utf-8");

            response.WriteString("Welcome to Azure Functions!");

            return response;
        }
    }

If UserRole contains admin then only "GetData" function will get execute otherwise it returns 403 (forbidden) response. Authorization middleware look like following.

/// <summary>
    /// Authorization middleware.
    /// </summary>
    public class AuthorizationMiddleware
        : IFunctionsWorkerMiddleware
    {
        private readonly ILogger<AuthorizationMiddleware> logger;

        public AuthorizationMiddleware(ILogger<AuthorizationMiddleware> logger)
        {
            this.logger = logger;
        }
        public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
        {
            if (context.IsHttpTriggerFunction())
            {
                string functionEntryPoint = context.FunctionDefinition.EntryPoint;
                Type assemblyType = Type.GetType(functionEntryPoint.Substring(0, functionEntryPoint.LastIndexOf('.')));
                MethodInfo methodInfo = assemblyType.GetMethod(functionEntryPoint.Substring(functionEntryPoint.LastIndexOf('.') + 1));
                if (methodInfo.GetCustomAttribute(typeof(FunctionAuthorizeAttribute), false) is FunctionAuthorizeAttribute functionAuthorizeAttribute)
                {
                    if (context.Items.ContainsKey("UserRole") && context.Items["UserRole"] != null)
                    {
                        var role = context.Items["UserRole"].ToString();
                        if (!functionAuthorizeAttribute.Roles.Contains(role, StringComparer.InvariantCultureIgnoreCase))
                        {
                            await context.CreateJsonResponse(System.Net.HttpStatusCode.Forbidden, new { Message = "Forbidden Access." });
                        }
                        else
                        {
                            await next(context);
                        }
                    }
                    else
                    {
                        await context.CreateJsonResponse(System.Net.HttpStatusCode.Forbidden, new { Message = "Forbidden Access." });
                    }
                }
            }
            else
            {
                await next(context);
            }
        }

Note : Above code is for example purpose. It does not consider performance or production related things.

It can be configured following way.

public static void Main()
    {
        var host = new HostBuilder()
            .ConfigureFunctionsWorkerDefaults(configure=>
            {
                configure.UseMiddleware<BearerAuthenticationMiddleware>();
                configure.UseMiddleware<AuthorizationMiddleware>();
            })
            .Build();

        host.Run();
    }

Download sample from following location. Download