Do You Know Azure Functions Have Function Filters?

I expect that you probably are not aware of Function-Filters in Azure Functions, just like I wasn't until a few days ago. Recently, I was working on an Azure Functions API based project and was thinking what if we had a functionality similar to ASP.NET MVC Filters in Azure Functions as well. It would have helped us solve the problem of customizing or requesting pipelines or sharing common logic across Azure Functions.

If you want to see how Function Filters can help us solve some real-life problems, this article is for you.
 
If you are a .NET developer, you will have implemented ASP.NET MVC Filters in your web applications previously.
 
In this article, we will see how Function-Filters are very similar to ASP.NET Filters and we will implement them in HTTP Trigger Azure Function to solve a real-life problem and discuss the pros and cons of using it.
 

Before Diving in

 
I hope you are aware of Azure Functions and ASP.NET MVC Filters in order to understand this article. If not, I would suggest you read this really awesome article written by Nitin Pandit: Filters in ASP.NET MVC 5.0.
 
I hope, by now, you will be having at least the basic knowledge of MVC Filters so that you should be able to understand Function-Filters in Azure Functions. Here is a figure demonstrating MVC filters and implementation.
 
Do You Know Azure Function Have Function Filters 
 
MVC Filters are used to execute some preprocessing and postprocessing logic.
 

Examples

  1. Authorization Filter: Checking whether Logged In user is valid or not
  2. Exception Filter attribute whenever there is an error raised by the controller and you want to execute your logic after that.
  3. Custom Filters are attributes created by you for your own custom requirement; for example - to check the permission of a logged-in user.
So now, the question arises - what kind of Filters are available in Function-Filters?
  1. FunctionInvocationFilterAttribute
  2. FunctionExceptionFilterAttribute
FunctionInvocationFilterAttribute
 
This filter is used to execute PRE and POST processing logic when a target job function is invoked.
 
FunctionExceptionFilterAttribute
 
Exception Filter will be called whenever there is an exception thrown by the Azure Function.
 
Function-Filters can be applied globally to all functions at the Class level or the Function level.
 
Prerequisites
  1. Visual Studio 2017 with Cloud SDKs installed
Let's now create an HTTP Trigger Azure Function using Visual Studio 2017.
 
Do You Know Azure Function Have Function Filters
 
Do You Know Azure Function Have Function Filters
 
HTTP Trigger Template Code
  1. using System;  
  2. using System.IO;  
  3. using System.Threading.Tasks;  
  4. using Microsoft.AspNetCore.Mvc;  
  5. using Microsoft.Azure.WebJobs;  
  6. using Microsoft.Azure.WebJobs.Extensions.Http;  
  7. using Microsoft.AspNetCore.Http;  
  8. using Microsoft.Extensions.Logging;  
  9. using Newtonsoft.Json;  
  10. using Microsoft.Azure.WebJobs.Host;  
  11. using System.Threading;  
  12.   
  13. namespace FunctionFilters  
  14. {  
  15.     public static class Function1  
  16.     {  
  17.         [FunctionName("Function1")]  
  18.         public  async Task<IActionResult> Run(  
  19.             [HttpTrigger(AuthorizationLevel.Function, "get""post", Route = null)] HttpRequest req,  
  20.             ILogger log)  
  21.         {  
  22.             log.LogInformation("C# HTTP trigger function processed a request.");  
  23.   
  24.             string name = req.Query["name"];  
  25.   
  26.             string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
  27.             dynamic data = JsonConvert.DeserializeObject(requestBody);  
  28.             name = name ?? data?.name;  
  29.   
  30.             return name != null  
  31.                 ? (ActionResult)new OkObjectResult($"Hello, {name}")  
  32.                 : new BadRequestObjectResult("Please pass a name on the query string or in the request body");  
  33.         }  
  34.   
  35.           
  36.     }  
  37. }  
Now, in order to implement Function-Filter in our Function class, we first need to implement Function-Filters from
IFunctionExceptionFilter and IFunctionInvocationFilter.
 
We have to change our class from static to instance Function and implement both of these Function Filters at class level and override their respective methods OnExceptionAsync, OnExecutedAsync and OnExecutingAsync.
  1. using Microsoft.AspNetCore.Http;  
  2. using Microsoft.AspNetCore.Mvc;  
  3. using Microsoft.Azure.WebJobs;  
  4. using Microsoft.Azure.WebJobs.Extensions.Http;  
  5. using Microsoft.Azure.WebJobs.Host;  
  6. using Microsoft.Extensions.Logging;  
  7. using Newtonsoft.Json;  
  8. using System;  
  9. using System.Diagnostics;  
  10. using System.IO;  
  11. using System.Threading;  
  12. using System.Threading.Tasks;  
  13.   
  14. namespace FunctionFilters  
  15. {  
  16.     public  class Function1:IFunctionExceptionFilter, IFunctionInvocationFilter  
  17.     {  
  18.         [FunctionName("Function1")]  
  19.         public  async Task<IActionResult> Run(  
  20.             [HttpTrigger(AuthorizationLevel.Function, "get""post", Route = null)] HttpRequest req,  
  21.             ILogger log)  
  22.         {  
  23.   
  24.             Debug.WriteLine("C# HTTP trigger function processed a request.");  
  25.   
  26.             string name = req.Query["name"];  
  27.   
  28.             string requestBody = await new StreamReader(req.Body).ReadToEndAsync();  
  29.             dynamic data = JsonConvert.DeserializeObject(requestBody);  
  30.             name = name ?? data?.name;  
  31.   
  32.             return name != null  
  33.                 ? (ActionResult)new OkObjectResult($"Hello, {name}")  
  34.                 : new BadRequestObjectResult("Please pass a name on the query string or in the request body");  
  35.         }  
  36.   
  37.         public Task OnExceptionAsync(FunctionExceptionContext exceptionContext, CancellationToken cancellationToken)  
  38.         {  
  39.             Debug.WriteLine($"Exception raised by the application {exceptionContext.Exception.ToString()}");  
  40.             return Task.CompletedTask;  
  41.         }  
  42.   
  43.         public Task OnExecutedAsync(FunctionExecutedContext executedContext, CancellationToken cancellationToken)  
  44.         {  
  45.             Debug.WriteLine($"I should be executed at last");  
  46.             return Task.CompletedTask;  
  47.         }  
  48.   
  49.         public Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)  
  50.         {  
  51.             Debug.WriteLine($"I should be executed at before function code");  
  52.             return Task.CompletedTask;  
  53.         }  
  54.     }  
  55. }  
You can see the implemented Function Filters look quite similar to ASP.NET MVC Filter.
  • OnExceptionAsync
    Filter falls under the category of invocation function filter which will get executed whenever an exception occurs in our function.

  • OnExecutingAsync
    Filter falls under the category of invocation function which will get executed before our function logic is executed and this helps us executing any kind of preprocessing logic.

  • OnExecutedAsync
    Filter falls under the category of Exception Filters and gets executed after our function logic is executed and this helps us executing any kind postprocessing logic.
Let's run the Function Filters by executing them in our local environment. In order to test the HTTP Trigger Azure Function, I will be using Postman which is an HTTP REST client.
 

Demo of OnExecutingAsync and OnExecutedAsync Function Filters

 
Do You Know Azure Function Have Function Filters
 

Demo of OnExceptionAsync Filter

 
In order to test the OnExceptionAsync, I am throwing a DivideByZeroException in our code so that the function will crash and our OnExceptionAsync filter will be invoked.
 
Do You Know Azure Function Have Function Filters
 
Testing the function with Postman and checking the order of the Function Filters.
 
Do You Know Azure Function Have Function Filters
 

Life Cycle of Function Filters

 
Do You Know Azure Function Have Function Filters
 
Now, we are aware of Function Filter in Azure Function. This is the time to think where we can use Function Filters in practical real-life scenarios. Well, we can use Function Filters in some of these scenarios 
  • Generic Exception Handlers for all Azure Functions
  • Sharing common logic across all HTTP Trigger Azure Function

Case Study

 
Enterprise Application Architecture of API Management and HTTP Trigger Azure functions.
 
Do You Know Azure Function Have Function Filters
 
From the above architecture, we can see the Authorization is happening at Azure Function level. So you can imagine that we would have written a duplicate code of checking the Authorization bearer token in each Azure function and now we will try to move that code to Authorize Function Filter.
 
In order to share a common logic across all HTTP trigger Azure functions, I want to create a generic Authorization Filter for all of my HTTP Azure functions to check the HTTP header for the JWT token. And, if the request headers don't contain an Authorization Bearer token we will reject the request with Unauthorized. This seems to be like a real-life scenario because I want to keep my Authorization Logic code in Filter and reuse all the Azure functions which should be accessed by valid users.
 
In an Enterprise Application normally we have API Management as a middle man who forwards only a valid request to the Azure Function. API management has many benefits that you can read from this awesome Benefits of using Azure API Management with micro-services.
 
In our implementation, we will be using HTTP triggered Azure functions but no API management service. We will create an Authorization Function Filter to check the JWT token in each request and if the user sends an Invalid JWT token we will return Unauthorized Response status to the User.
 
Let’s get started and create our HTTP Trigger Azure Function using Visual Studio and name it as per your preference.
 
Do You Know Azure Function Have Function Filters
 
Select HTTP Trigger Template and select Azure Functions V1 because in version V2 I had some issues with HTTP trigger function when I tested on my local machine while writing this.
 
Do You Know Azure Function Have Function Filters
 
Create a new Class in our solution and name it as FunctionAuthorizeAttribute:
 
Implement FunctionInvocationFilterAttribute and override only OnExecutingAsync method.
 
Do You Know Azure Function Have Function Filters 
 
Create a ValidationPackage Class which will store the properties of JWT token when extracted. If you are new to the JWT Token concept my honest suggestion will be go and learn the basics of JWT from here and get your JWT token for testing.
 
You can extract the JWT token from the JWT website.
 
Do You Know Azure Function Have Function Filters
 
  1. public class ValidationPackage {  
  2.     public bool ValidToken {  
  3.         get;  
  4.         set;  
  5.     }  
  6.     public string PrincipalName {  
  7.         get;  
  8.         set;  
  9.     }  
  10.     public string Scope {  
  11.         get;  
  12.         set;  
  13.     }  
  14.     public string AppID {  
  15.         get;  
  16.         set;  
  17.     }  
  18.     public long IssuedAt {  
  19.         get;  
  20.         set;  
  21.     }  
  22.     public long ExpiresAt {  
  23.         get;  
  24.         set;  
  25.     }  
  26.     public string Token {  
  27.         get;  
  28.         set;  
  29.     }  
  30.     public string LastName {  
  31.         get;  
  32.         internal set;  
  33.     }  
  34.     public string FirstName {  
  35.         get;  
  36.         internal set;  
  37.     }  
  38.     public ValidationPackage() {  
  39.         ValidToken = false;  
  40.     }  
  41. }  

FunctionAuthorizeAttribute code

 
Now in the OnExecutingAsync function we will be writing our logic to read Bearer token from request header and extract claims out of the token and validate it. If the token is valid we will add a new Header to the request to set whether token is valid or not because as per our current implementation we can't short circuit the pipeline and send a response from Function filters.
 
In this scenario we are just extracting a JWT token, not validating if it is registered to our product or not. Make sure you validate the identity as well so that any other JWT token passed will not execute the Azure function.
 
If the token is valid we are adding a header AuthorizationStatus to the request that stores the HttpStatus code whether it is Accepted or Unauthorized.
  1. public class FunctionAuthorizeAttribute : FunctionInvocationFilterAttribute  
  2.    {  
  3.        public FunctionAuthorizeAttribute()  
  4.        {  
  5.        }  
  6.   
  7.        public override Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)  
  8.        {  
  9.            var workItem = executingContext.Arguments.First().Value as HttpRequestMessage;  
  10.            ValidationPackage validationPackage = new ValidationPackage();  
  11.            AuthenticationHeaderValue jwtInput = workItem.Headers.Authorization;  
  12.   
  13.            if (jwtInput != null)  
  14.            {  
  15.                String jwt = "";  
  16.                if (jwtInput.ToString().StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))  
  17.                {  
  18.                    jwt = jwtInput.ToString().Substring("Bearer ".Length).Trim();  
  19.                }  
  20.   
  21.                JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();  
  22.   
  23.                  try  
  24.                    {  
  25.                        validationPackage = ExtractClaims(jwt, handler);  
  26.                    }  
  27.                    catch (Exception ex)  
  28.                    {  
  29.                        throw ex;  
  30.                  }                 
  31.            }  
  32.   
  33.            if(!validationPackage.ValidToken)  
  34.            {  
  35.                workItem.Headers.Add("AuthorizationStatus",  Convert.ToInt32(HttpStatusCode.Unauthorized).ToString());  
  36.            }  
  37.            else  
  38.            {  
  39.                workItem.Headers.Add("AuthorizationStatus", Convert.ToInt32(HttpStatusCode.Accepted).ToString());            
  40.            }  
  41.            return base.OnExecutingAsync(executingContext, cancellationToken);  
  42.        }  
  43.        public static ValidationPackage ExtractClaims(string jwt, JwtSecurityTokenHandler handler)  
  44.        {  
  45.            ValidationPackage validationPackage = new ValidationPackage();  
  46.   
  47.            validationPackage.Token = jwt;  
  48.   
  49.            var token = handler.ReadJwtToken(jwt);  
  50.            validationPackage.Scope = "user_impersonation";  
  51.   
  52.            try  
  53.            {  
  54.                 
  55.                var claims = token.Claims;  
  56.                foreach (Claim c in claims)  
  57.                {  
  58.                    switch (c.Type)  
  59.                    {  
  60.                        case "sub":  
  61.                        case "upn":  
  62.                            if (c.Value.Contains('@'))  
  63.                                validationPackage.PrincipalName = c.Value;  
  64.                            break;  
  65.   
  66.                        case "Firstname":  
  67.                            validationPackage.FirstName = c.Value;  
  68.                            break;  
  69.   
  70.                        case "Lastname":  
  71.                            validationPackage.LastName = c.Value;  
  72.                            break;  
  73.   
  74.                        case "client_id":  
  75.                        case "aud":  
  76.                            validationPackage.AppID = c.Value;  
  77.                            break;  
  78.   
  79.                        case "iat":  
  80.                            validationPackage.IssuedAt = Convert.ToInt64(c.Value);  
  81.                            break;  
  82.   
  83.                        case "exp":  
  84.                            validationPackage.ExpiresAt = Convert.ToInt64(c.Value);  
  85.                            break;  
  86.   
  87.                        case "scp":  
  88.                            validationPackage.Scope = c.Value;  
  89.                            break;  
  90.                    }  
  91.                }  
  92.            }  
  93.            catch (Exception e)  
  94.            {  
  95.                validationPackage.ValidToken = false;  
  96.            }  
  97.            var currentTimestamp = (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0)).TotalSeconds;  
  98.   
  99.            if ((validationPackage.ExpiresAt - currentTimestamp) > 0)  
  100.                validationPackage.ValidToken = true;  
  101.            return validationPackage;  
  102.        }  
  103.   
  104.    }  
Now we need to add the Function Filter Class as an Attribute to our Http Triggered Azure function as shown in below code snippet.
  1. public static class GetCustomerById  
  2.     {  
  3.         [FunctionName("GetCustomerById")]  
  4.         [FunctionAuthorize]  
  5.         public static async Task<IActionResult> Run(  
  6.             [HttpTrigger(AuthorizationLevel.Function, "get""post", Route = null)] HttpRequestMessage req,  
  7.             ILogger log)  
  8.         {  
  9.   
  10.             string authorizationStatus = req.Headers.GetValues("AuthorizationStatus").FirstOrDefault();           
  11.             if (Convert.ToInt32(authorizationStatus).Equals((int)HttpStatusCode.Accepted))  
  12.             {  
  13.                 log.LogInformation("C# HTTP trigger function processed a request.");  
  14.                 var customers = Builder<Customer>  
  15.                 .CreateListOfSize(10)  
  16.                 .All().Build();      
  17.                 int Id = 1;  
  18.   
  19.                 var customer = customers.Where(x => x.Id == Id);  
  20.                 return customer != null  
  21.                     ? (ActionResult)new OkObjectResult(customer)  
  22.                     : new BadRequestObjectResult("Please pass a name on the query string or in the request body");  
  23.             }  
  24.             else  
  25.             {  
  26.                 return new UnauthorizedResult();  
  27.             }  
  28.         }  
  29.   
  30.     }  
You can see that we have added [FunctionAuthorize] to our Function and we are validation and we checking the request headers to check if request had valid JWT Token or not?
  1. string authorizationStatus = req.Headers.GetValues("AuthorizationStatus").FirstOrDefault();           
  2.             if (Convert.ToInt32(authorizationStatus).Equals((int)HttpStatusCode.Accepted))  
  3.             {  
  4.             }   
  5.             else  
  6.   
  7.             {  
  8.                 return new UnauthorizedResult();  
  9.             }  
Let's now run our function app test it without JWT token and check if it working as per our expectation or not.

Do You Know Azure Function Have Function Filters

Testing it with a valid JWT Token

 
Copy the token from AuthO website.
 
Do You Know Azure Function Have Function Filters
 
Add the JWT Token to the request header as shown below and then press Send.
 
Do You Know Azure Function Have Function Filters
 
Do You Know Azure Function Have Function Filters
 
Wow, you can see that we are able to Authenticate the JWT token with the help of Function Filter. We can reuse the same logic across all other HTTP trigger Azure functions without duplicating the logic and keeping the Authorization logic separate.
 
So we learned how we can leverage the power of Function Filters in Azure function.
 
But there is a down side of using Function Filters as of now because they are not matured enough and cannot short circuit the request back to the User. As we saw we are kind of adding a request header and then checking the header.
 
While developing you will also see deprecated or obsolete message on the Function filters and that might prevent you from using it in production. I had the same question so I raised it with the Azure function team and below is their reply. You can check the whole discussion out here.
 
Do You Know Azure Function Have Function Filters
 
But please note the implementation is subjective to change in the near future so be aware of it.
 
I hope you enjoyed reading the article as much as I did writing it up. It's so cool to know Function Filters and how they can help us share common logic across Azure Functions.
 
If you did, leave your thoughts in the comments below.
 
Also, I will love it if you share the article on your preferred social media channel.
 
References


Similar Articles