Eduard Keilholz

Hi, my name is Eduard Keilholz. I'm a Microsoft developer working at 4DotNet in The Netherlands. I like to speak at conferences about all and nothing, mostly Azure (or other cloud) related topics.
LinkedIn | Twitter | Mastodon | Bsky


I received the Microsoft MVP Award for Azure

Eduard Keilholz
HexMaster's Blog
Some thoughts about software development, cloud, azure, ASP.NET Core and maybe a little bit more...

Azure Functions Jwt Validator Binding

Azure Functions It’s #ServerlessSeptember so time for some awesome serverless stuff. This time, writing a custom binding. When you want to use a login provider other than Azure AD, you want to validate incoming requests and make sure the caller is authorized. Doing this each and every request with a copied and pasted piece of code is not very convenient. So today we’ll be writing a custom Azure Functions binding, validating JWT Tokens for functions with an HTTP Trigger.

Azure Functions Introduction

So first, a brief introduction of what an Azure Function is. As a software engineer, you’ve probably written a function somewhere. Well, guess what, an Azure Function is the same. It’s just a couple of lines of code, grouped into what we call a function. An Azure Function requires a trigger and optionally contains bindings. Read more information about Azure Functions right here.

Triggers

Triggers are (again, it’s in the name) the one that causes the function to be executed. There are different types of triggers:

  • HTTP
  • Queues / Topics
  • Timer
  • Many (MANY) more

So for example, an HTTP trigger enables you to trigger the function as soon as an HTTP request is received. Queues and Topic triggers fire when a message arrives on one of the many messaging mechanisms that Azure supports.

Bindings

Bindings can be divided into two separate categories, input, and output bindings. Input bindings allow you to input information from somewhere into your function. For example, a CosmosDB Input binding allows you to read data from CosmosDB and ‘inject’ that as a parameter into your function. Output bindings do the opposite. You can use output bindings to send/store information to other systems. The amount of bindings is enormous, you can read and/or write from and to almost anywhere. And in case your desired binding doesn’t exist, you can write one yourself!

About authorization

When you’re using Microsoft Azure, and make use of Azure AD (Active Directory), you can easily validate requests and make sure the caller of your function is allowed to execute its code. However, when you’re using different token providers like Auth0 or Google, it’s a different story. You may also use Identity Server for example to run your own token provider. In that case, JWT Tokens are handed out to authorized users. Their clients (for example a website), may use this JWT Token in a Web Request to your Azure Function (with HTTP Trigger). Now, you need to validate the JWT Token yourself to be confident enough the caller is who he says he is.

JWT Tokens

Although JWT Tokens seem to be pretty easy, there’s a lot to validate. This makes them secure, but also make them a little bit hard to understand if you’re not a security person. Information is stored in something that’s called a claim. A claim has a type and a value (or values). You can extend the JWT Token with custom claims if you like. Below is a list of claims I think are important to validate:

  • Issuer (iss): This is a reference to the ‘authority’ that provided the token. Most of the time, this is the endpoint to your token provider.
  • Client (cln): This is a software system registered by the issuer and may ‘communicate’ with the token provider.
  • Not before (nbf): This is a timestamp at which the token can be used. So the token is not valid before this time.
  • Expiration time (exp): This is a timestamp indicating when the token expires
  • Audience (aud): Who or what the token is intended for. When the token is handed out, it’s handed out for a specific reason (for example, to be able to call your Azure Function). This audience field must then contain a reference to your Azure Function. A token can contain 0 or more audiences.
  • Scopes (scp): When a system requests permission to access a resource through your token provider, it uses the scope parameter to specify what access it needs.

Finally, and this is an important step, is the signature. A JWT Token consists of three parts separated by dots. A JWT Token will look like xxxx.yyyy.zzzz. The xxxx part is the Header, the yyyy part the Payload, and the zzzz part the Signature. The Header contains information about the type of token and the algorithm used for signing the token. The Payload contains all your claims. The Signature is a signature added by your token provider. Validating the signature completes the validity check of the token. If the signature check fails, the token should be considered invalid.

Back to business

So now you know the basics about authorization and JWT Tokens, we can start writing our custom Azure Functions binding. Let’s just dig into code:

[Extension("JwtBinding")]
public class JwtBinding : IExtensionConfigProvider
{
    public void Initialize(ExtensionConfigContext context)
    {
        var rule = context.AddBindingRule<JwtBindingAttribute>();
        rule.BindToInput(BuildItemFromAttribute);
    }

    private AuthorizedModel BuildItemFromAttribute(JwtBindingAttribute arg)
    {
        // Do scary stuff
        return null;
    }
}

The above code is the boilerplate you need to write your own Binding. Inside the BuildItemFromAttribute, we’re going to fetch all information we need to validate incoming tokens and call the actual validation method. But first, we need to register our Binding so it can be used with that nice [JwtBinding] attribute.

To do this, I created an extension class (JwtBindingExtension.cs) that registers my binding with the WebJobsBuilder.

public static class JwtBindingExtension
{
    public static IWebJobsBuilder AddJwtBindingExtension(this IWebJobsBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }

        builder.AddExtension<JwtBinding>();
        return builder;
    }
}

Finally, we need to hook up to the WebJob’s Startup to call the AddJwtBindingExtension() method above.

[assembly: WebJobsStartup(typeof(JwtBindingStartup))]
namespace HexMaster.Functions.JwtBinding
{
    public class JwtBindingStartup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.AddJwtBindingExtension();
        }
    }
}

All good, your custom binding is now ready. But we still need the Attribute to indicate we’re going to make use of this binding. This attribute allows developers (users) to pass parameters to the binding that you want to use to do stuff. In our case, we’re going to allow users to pass validation information of tokens so that we can accurately validate JWT Tokens. The attribute looks like this:

[Binding]
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.ReturnValue)]
public class JwtBindingAttribute : Attribute
{

    public JwtBindingAttribute(string issuer = null, string audience = null)
    {
        Issuer = issuer;
        Audience = audience;
    }

    public string Scopes { get; set; }
    [AutoResolve] public string Audience { get; set; }
    [AutoResolve] public string Issuer { get; set; }

}

That’s it, your now good to go. You can compile the code, and use the JWT Binding attribute in your function. I created a simple function with a HTTP Trigger so I can test the binding like so:

[FunctionName("AwesomeJwtFunction")]
public async Task<IActionResult> Run(
    [HttpTrigger("get", Route = "your-route")] HttpRequestMessage req,
    [JwtBinding("%JwtBinding:Issuer%", "%JwtBinding:Audience%")] AuthorizedModel auth
    )
{
    return new OkResult();
}

Awesome, the binding is there, and it’s used. You can see in the usage of the binding, that I pass values surrounded by percentage signs. This means ‘get the value from configuration’. So if you have a configuration file and add a valid value for JwtBinding:Issuer and JwtBinding:Audience, you’re good to go. In case you run locally, just add the values to the Values collection in your local.settings.json file.

  "Values": {
    "JwtBinding:Audience": "your-api-name",
    "JwtBinding:Issuer": "https://token-provider.com/login"
  }

Token Validation

The ASP.NET Core framework contains standard classes for validating JWT Tokens. You just need to set a couple of parameters and call the validate function. Because we want the binding to be as flexible and reusable as possible, we’re going to rely on the configuration as much as possible. Users must be able to turn certain types of validation on or off depending on their needs.

To validate the token, I use the JwtSecurityTokenHandler Class in System.IdentityModel.Tokens.Jwt. You pass the token and parameters in, and it returns a token or throws an exception when validation fails. In case validation succeeds, I grab some information from the token to return it so the user can use this information further up in the HTTP request.

The current version only supports validation on dates (Not Before, and Expiration), Issuer, and Audience. If you don’t pass an audience, it will not be validated. Same for the Issuer. I will be working on this library and add more validations to it soon.

Open Source

The source code of this binding is here, and changes are pushed to NuGet using GitHub Actions.

Next up, is validating scopes and the token signature. Feel free to send pull requests and in case you’re going to use this binding, please share your findings and insights using the Issues page in GitHub.

Again, help on this binding making it more secure and production-ready will be appreciated ;)

Create:Serverless

Create:Serverless In this 4 hour event, hear from our keynote speakers Chris Coyier and Chris Nwamba on the Serverless landscape in 2020 and the trends you should look out for.

You will also learn:

More about Create:Serverless

Update 2020-09-23

Removed disclaimer, signature validation is in place. Scope validation is still to-do