18 December 2018

Custom token authentication in Azure Functions using bindings

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.

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.

This can be done by creating a custom input binding that can be used to make the ClaimsPrincipal part of the function definition. 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, 
    [AccessTokenClaimsPrincipal principal)
{
    log.LogInformation($"Request received for {principal.Identity.Name}.");
    return new OkResult();
}

This approach eliminates the need for boiler plate and makes the validation of access tokens an external concern. It also makes the function more 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.

[assemblyWebJobsStartup(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);

A caveat – returning an appropriate HTTP status

Note that this implementation is incomplete in one important respect. Any validation failures will throw an exception to the Azure Functions run-time and a 500 Internal Server Error status code will be returned to the user with no message. There is no way of interrupting this response and returning something more useful to the client, e.g. a 403 Forbidden response.

To do this you’ll need to wrap the principal into along with a status and any exceptions that occurred while validating the token. This wrapped result should be returned to the Azure function which will then be responsible for interpreting the result and returning the appropriate HTTP response.

Example source code (GitHub)

Filed under .Net Core, Azure, Serverless.