Custom token authentication in Azure Functions

Azure Functions only provides direct support for OAuth access tokens that have been issued by a small number of providers, such as Azure Active Directory, Google, Facebook and Twitter. If you want to validate tokens issued by an external OAuth server or integrate with a custom solution, you’ll need to create the plumbing yourself.

In the .Net world the ideal mechanism would be to find some way of injecting a ClaimsPrincipal instance into the running function. Validating access tokens based on Json Web Tokens (JWTs) is relatively straightforward, but there’s no middleware in Azure Functions that you inject the result into a function.

You could add some boiler plate at the beginning of every function, but this is a little messy and difficult to test. Ideally you need to separate function definitions from the authentication mechanism they are using, so they can just consume a ClaimsPrincipal that has been created elsewhere.

[May 2019 Update] Now that dependency injection has finally been added to the functions run-time there are two ways of doing this: injecting a factory into the function constructor or using custom input binding to inject the validated principal directly into the function method. Using the built-in dependency injection is cleaner, involves less code and is the approach I would take for any new projects. However, custom input binding does have the advantage of injecting the result directly into a function rather than being something you have to remember to add.

Sample code for both of these approaches is available on GitHub but this post walks through both implementations.

Using custom input binding

Injecting a principal directly into the function definition eliminates the need for any boiler plate. You don't have to remember to validate the principal - it's just sitting there for you. The method signature below shows what this looks like –  the principal argument has been decorated with a custom binding argument called AccessToken. 

[FunctionName("ExampleHttpFunction")]
public static async Task<IActionResult> Run(
    [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req, 
    ILogger log, 
    [AccessToken] AccessTokenResult principal)
{
    if (principal.Status == AccessTokenStatus.Valid)
    {
        log.LogInformation($"Request received for {principal.Principal.Identity.Name}.");
        return new OkResult();
    }
    else
    {
        return new UnauthorizedResult();
    }
}

The AccessTokenResult is just a custom class that encapsulates the result of the validation. It contains the validated principal but it also contains any errors that were thrown during the validation process. This allows the function to determine how best to respond to any invalid tokens, i.e. by returning a 401 Unauthorized response.

This approach minimises any boiler plate and makes the validation of access tokens an external concern. It also makes the function testable as you can inject security principals into the function from test code.

The full code for this example is posted in GitHub, but the idea was taken from Boris Wilhem's on-going work around implementing dependency injection in Azure Functions. He uses a similar approach to allow you to define dependencies in start-up code that are injected into methods at run-time.

Creating the custom input binding

The implementation involves creating half a dozen small classes to wire everything into the Functions SDK:

  • An attribute that is used to annotate the ClaimsPrincipal argument in the function definition
  • A custom binding made up of three classes that reads the access token in the incoming request and creates a ClaimsPrincipal to be returned to the function
  • An extension configuration provider that wires the attribute and the custom binding together.
  • An extension method that lets you register the binding when the Azure Function host starts up.

The attribute definition can be a simple, empty attribute class definition that is decorated with a Binding attribute.

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

Custom bindings can be straightforward, though this implementation is complicated by the need to access the underlying HTTP request for the access token. This requires three classes:

  • A custom binding provider that implements IBindingProvider – this will be associated directly to the attribute definition.
  • The provider will be expected to return a binding that implements IBinding. The binding class is where you have access to the underlying request context so can obtain application settings and the HTTP request header.
  • The binding will be responsible for returning a value provider that implements IValueProvider. This is where you do the work to crack open the JWT and create the ClaimsPrinciple.

Basic versions of these classes are shown in the listing below:

/// <summary>
/// Provides a new binding instance for the function host.
/// </summary>
public class AccessTokenBindingProvider : IBindingProvider
{
    public Task<IBinding> TryCreateAsync(BindingProviderContext context)
    {
        IBinding binding = new AccessTokenBinding();
        return Task.FromResult(binding);
    }
}
    
/// <summary>
/// Runs on every request and passes the function context (e.g. Http request and host configuration) to a value provider.
/// </summary>
public class AccessTokenBinding : IBinding
{
    public Task<IValueProvider> BindAsync(BindingContext context)
    {
        // Get the HTTP request
        var request = context.BindingData["req"] as DefaultHttpRequest;
    
        // Get configuration settings
        var issuerToken = Environment.GetEnvironmentVariable("IssuerToken");
    
        // Return a value provider
        return Task.FromResult<IValueProvider>(new AccessTokenValueProvider(request, issuerToken));
    }
}
    
/// <summary>
/// Creates a <see cref="ClaimsPrincipal"/> instance for the supplied header and configuration values.
/// </summary>
public class AccessTokenValueProvider : IValueProvider
{
    private HttpRequest _request;
    private readonly string _issuerToken;
    
    public AccessTokenValueProvider(HttpRequest request, string issuerToken)
    {
        _request = request;
        _issuerToken = issuerToken;
    }
    
    public Task<object> GetValueAsync()
    {
        // This is where we implement the actual authentication...
    }
}

To wire attribute and binding together an extension configuration provider is required that implements IExtensionConfigProvider. All this class does is define a rule for the attribute definition that will be picked up by the Azure Functions runtime. This rule can associate the attribute with a custom binding as shown below:

public class AccessTokenExtensionProvider : IExtensionConfigProvider
{
    public void Initialize(ExtensionConfigContext context)
    {
        // Creates a rule that links the attribute to the binding
        var provider = new AccessTokenBindingProvider();
        var rule = context.AddBindingRule<AccessTokenAttribute>().Bind(provider);
    }
}

Finally, you’ll need to tell the Azure Functions host about the binding when it starts up. Firstly, you create an extension method that lets you add the binding to the host’s IWebJobsBuilder context as shown below:

/// <summary>
/// Called from Startup to load the custom binding when the Azure Functions host starts up.
/// </summary>
public static class AccessTokenExtensions
{
    public static IWebJobsBuilder AddAccessTokenBinding(this IWebJobsBuilder builder)
    {
        if (builder == null)
        {
            throw new ArgumentNullException(nameof(builder));
        }
    
        builder.AddExtension<AccessTokenExtensionProvider>();
        return builder;
    }
}

This code is executed in a custom Startup method that you’ll need to add to your project. The code below demonstrates this – note the use of the assembly attribute that tells the Azure Functions runtime to use the Startup class when the host initializes.

[assembly: WebJobsStartup(typeof(Startup))]
namespace FunctionsCustomSercuity
{
    /// <summary>
    /// Runs when the Azure Functions host starts.
    /// </summary>
    public class Startup : IWebJobsStartup
    {
        public void Configure(IWebJobsBuilder builder)
        {
            builder.AddAccessTokenBinding();
        }
    }
}

Validating the token

All the work around token validation happens in the value provider class - AccessTokenValueProvider. This should receive all the configuration and context information it needs from the binding class, allowing for a clean and testable implementation that generates a ClaimsPrincipal from the incoming token.

The Microsoft.IdentityModel.JsonWebTokens and System.IdentityModel.Tokens.Jwt NuGet packages contain all the libraries needed to validate JWT access tokens. The first step is to define the TokenValidationParameters used in decoding the token. The example below will perform the following validation:

  • The token will be decrypted using the key specified in the IssuerSigningKey property.
  • It will also validate the token’s issuer and intended audience against the values in the ValidIssuer and ValidAudience properties
  • The token’s lifetime will be checked to ensure that it hasn’t expired.

Assuming that the token is being supplied as a "bearer token", you’ll need to take it from the “Authorization” header and strip off the leading "Bearer " text. The actual token validation only requires a few lines of code:

// Create the parameters
var tokenParams = new TokenValidationParameters()
{
    RequireSignedTokens = true,
    ValidAudience = _audience,
    ValidateAudience = true,
    ValidIssuer = _issuer,
    ValidateIssuer = true,
    ValidateIssuerSigningKey = true,
    ValidateLifetime = true,
    IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(_issuerToken))
};
    
// Validate the token
var handler = new JwtSecurityTokenHandler();
var result = handler.ValidateToken(token, tokenParams, out var securityToken);

Using dependency injection

Until the 1.0.28 release of Azure Functions, custom bindings was pretty much the only way of using a custom OAuth provider with Azure Functions. Now you can use dependency injection to create a factory class that can return a validated principal from an Http request. The interface definition below is an example of the kind of factory that can be injected. AccessTokenResult just wraps the validated principal along with any errors encountered during the validation process.

public interface IAccessTokenProvider
{
    AccessTokenResult ValidateToken(HttpRequest request);
}

You can inject an implementation of this in the new FunctionsStartup class provided in the new Microsoft.Azure.Functions.Extensions package as shown below:

public override void Configure(IFunctionsHostBuilder builder)
{
    // Get the configuration files for the OAuth token issuer
    var issuerToken = Environment.GetEnvironmentVariable("IssuerToken");
    var audience = Environment.GetEnvironmentVariable("Audience");
    var issuer = Environment.GetEnvironmentVariable("Issuer");
    
    // Register the access token provider as a singleton
    builder.Services.AddSingleton<IAccessTokenProvider, AccessTokenProvider>(s => new AccessTokenProvider(issuerToken, audience, issuer));
}

The actual function class will have a constructor that receives an instance of IAccessTokenProvider. You will need to remember to invoke the factory's ValidateToken method for every function request as shown below:

public class ExampleHttpFunction
{
    private readonly IAccessTokenProvider _tokenProvider;
    
    public ExampleHttpFunction(IAccessTokenProvider tokenProvider)
    {
        _tokenProvider = tokenProvider;
    }
    
    [FunctionName("ExampleHttpFunction")]
    public IActionResult Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "example")] HttpRequest req,  ILogger log)
    {
        var result = _tokenProvider.ValidateToken(req);
    
        if (result.Status == AccessTokenStatus.Valid)
        {
            log.LogInformation($"Request received for {result.Principal.Identity.Name}.");
            return new OkResult();
        }
        else
        {
            return new UnauthorizedResult();
        }
    }
}

Sample code

The source code below contains examples for both approaches - custom tokens and dependency injection.

Example source code (GitHub)