Authenticating JWT Tokens with Azure Functions

So recently, I decided to work on creating some HTTP exposed Azure Functions to return data if a JWT token was valid and various 4xx response codes otherwise. Needless to say, I did not expect it to be as hard as it turned out to be, I would say that Microsoft has work to do to enables support of full blown APIs with Azure Functions provided they are not held behind an API Management gateway service; this may be what is intended.

How did I create my token?

So, I used JwtSecurityToken in the Microsoft.IdentityModel.Tokense Nuget package with a Symmetric Security Key to generate a signed signature. This was pretty easy – here is my token generation code:


public string CreateToken(User user)
{
if (user == null)
throw new ArgumentException(nameof(user));
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Username)
};
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var creds = new SigningCredentials(key, SecurityAlgorithms.HmacSha256Signature);
var token = new JwtSecurityToken(_configuration["JwtIssuer"],
_configuration["JwtAudience"],
claims,
expires: DateTime.Now.AddMonths(1),
signingCredentials: creds);
return new JwtSecurityTokenHandler().WriteToken(token);
}

view raw

token-create.cs

hosted with ❤ by GitHub

For our purposes we want to be able to decode the token to get some non confidential information (the username) so we can do some lookup for user related information – we could also choose to use the UserId as well here if we so desired (in fact we should if the user can change their username)

Decrypting the Token

Here is my code for decrypting the token above via a Read service I wrote as a common method for other Microservices:


public TokenReadResult ReadToken(string tokenString)
{
var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configuration["JwtKey"]));
var handler = new JwtSecurityTokenHandler();
var validations = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = key,
ValidateIssuer = true,
ValidIssuer = _configuration["JwtIssuer"],
ValidateAudience = true,
ValidAudience = _configuration["JwtAudience"]
};
var identity = handler.ValidateToken(tokenString, validations, out var tokenSecure).Identity as ClaimsIdentity;
if (identity == null)
{
throw new Exception("boom – Identity is not valid");
}
return new TokenReadResult
{
Username = identity.Claims.First(c => c.Type == ClaimTypes.NameIdentifier).Value,
};
}

view raw

read-token.cs

hosted with ❤ by GitHub

The important thing here is that we use the same Issuer, Audience, and Key as during the encryption process. Validate will use these values and check our token – there are a variety of exceptions that can come out of this operation so you will want to the call to be ready to catch them for the various error cases: <Docs>

Ok so that all is actually pretty easy, now lets get into the hard part. Our goal is, when our Azure Function is called we want to receive the parsed result from the JWT token so we can centralize this logic and use it across many functions.

Normally, the way you would do this is to create a Filter that checks the request and, if valid, passes the value to some sort of base class that holds our function. Often this requires DI since we are injecting our Read Service into the Filter. We support this in normal Web API with ServiceFilter. Unfortunately, Microsoft currently does not support this, or any approach that I could find for Azure Functions. So what do we do.

Introducing Extensions

So, the Function runtime does support custom extensions which can act, in a way, like filters do in .NET Core (Azure Functions do actually support Filters, they are just new and arent as feature rich as their MVC/WebAPI counterparts).

Using an extension we can make our Azure Function call look like the following:


[FunctionName("GetCurrentUserFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "current/user")]HttpRequestMessage eventData,
ILogger logger,
[UserToken]UserTokenResult userResult)
{
}

view raw

function_one.cs

hosted with ❤ by GitHub

Do you see it? UserToken is our custom extension. Its job is to look at the incoming request and grab the token, decode it, and pass an object with various bits of claim data. Be careful with what you pass, you do not want sensitive data in your claims data since anyone can head over to jwt.io and decode it and see your claims.

First Step: Create a Value Provider

Extensions are a means to call custom bindings. Bindings seek to provide a value. Azure Function host provides the IValueProvider that we need to implement to create our Value Provider. This class will perform the operation relevant to our custom binding. Below are the two pieces of this class that are relevant: Constructor and GetValueAsync


public UserTokenValueProvider(string userToken, string issuerToken, IReadTokenService readTokenService)
{
_userToken = userToken;
_issuerToken = issuerToken;
_readTokenService = readTokenService;
}
public async Task<object> GetValueAsync()
{
try
{
var readResult = _readTokenService.ReadToken(_userToken);
return new UserTokenResult { Username = readResult.Username, TokenState = TokenState.Valid };
}
catch (ArgumentNullException)
{
// token not provided
return new UserTokenResult { TokenState = TokenState.Empty };
}
catch
{
// token was bad
return new UserTokenResult { TokenState = TokenState.Invalid };
}
}

As I mentioned earlier, the Validate method (called by ReadToken) can throw a litany of exceptions depending on problems with the token. Ultimately, the value returned from here is what our Azure Function receives.

The reason I choose to include the constructor was to begin to illustrate how the ReadTokenService is hydrated – you will find that DI is rather limited at this level and requires some odd hacks to get it to work. We will get into it as we unwrap this.

Ok good, this is your value provider, now we need to create the binding which calls this.

Part 2: Create our Binding to call the Value Provider

The binding is the layer between the Extension and the Value Provider. It immediately receives a binding context that gives it information about the incoming request so we can extract information – this is where we get the raw headers that contain our token from. Here we implement the IBinding interface. Here is my constructor, ToParameterDescriptor, and BindingAsync(BindingContext context):


public UserTokenBinding(IServiceProvider serviceProvider)
{
_readTokenService = serviceProvider.GetService<IReadTokenService>();
_configuration = serviceProvider.GetService<IConfiguration>();
}
public Task<IValueProvider> BindAsync(BindingContext context)
{
var headers = context.BindingData["Headers"] as IDictionary<string, string>;
var issuerToken = _configuration["JwtIssuer"];
if (!headers.ContainsKey("Authorization"))
return Task.FromResult<IValueProvider>(new UserTokenValueProvider(string.Empty, issuerToken, _readTokenService));
var userTokenGroups = Regex.Match(headers["Authorization"], @"^Bearer (\S+)$").Groups;
var userToken = userTokenGroups.ElementAt(1).Value;
return Task.FromResult<IValueProvider>(new UserTokenValueProvider(userToken, issuerToken, _readTokenService));
}
public ParameterDescriptor ToParameterDescriptor()
{
return new ParameterDescriptor();
}

view raw

binding.cs

hosted with ❤ by GitHub

So, the first thing to unpack here is the constructor – technically there is NO DI support within extensions (for some reason). How I got around this was I passed IServiceProvider which is our DI Container that I can extract dependencies from – this is what I do via the Service Locator Pattern: we extract both our configuration facade and the service to read our token.

Where this comes into play is when we create our ValueProvider – we pass the service to read the token into the constructor as we create it.

The remaining code in BindAsync is our logic for extracting the raw token from the Auth header (if it is present) and passing it, again via the constructor, to our Value Provider.

As for the ParameterDescriptor, I dont really know what this is doing or what it is used for, it doesnt seem to have an impact, positive or negative, on this use case.

Ok, so no we have create a Binding which calls our Value Provider to carry out the operation. We use Service Locator pattern on the DI container to extract the dependencies that we need. Our next step is to create the Binding Provider

Part 4: Create the Binding Provider

Our extension calls a specific binding provider to get the binding to carry out the intended operation for the extension. This is driven using the IBindingProvider interface and implementing the TryCreateAsync method. For our example, this class is very tiny, I show it in its entirety below:


public class UserTokenBindingProvider : IBindingProvider
{
private readonly IServiceProvider _serviceProvider;
public UserTokenBindingProvider(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public Task<IBinding> TryCreateAsync(BindingProviderContext context)
{
IBinding binding = new UserTokenBinding(_serviceProvider);
return Task.FromResult(binding);
}
}

Again, you can see I pass IServiceProvider into this method via the constructor and then pass it to our binding which we described in the previous step.  I am sure you can see where this is going đŸ™‚

Part 5: Create the Extension Provider

We have finally arrived at the extension provider. This is where we register our Extension with the runtime so it can be used within our code. This implements the IExtensionConfigProvider to support the Initialize method:


public class UserTokenExtensionProvider : IExtensionConfigProvider
{
private readonly IConfiguration _configuration;
public UserTokenExtensionProvider(IConfiguration configuration)
{
_configuration = configuration;
}
public void Initialize(ExtensionConfigContext context)
{
var serviceCollection = new ServiceCollection();
serviceCollection.RegisterDependencies();
serviceCollection.AddSingleton<IConfiguration>(_configuration);
var serviceProvider = serviceCollection.BuildServiceProvider();
context.AddBindingRule<UserTokenAttribute>().Bind(new UserTokenBindingProvider(serviceProvider));
}
}

view raw

extension.cs

hosted with ❤ by GitHub

And this is where we can get our IServiceProvider reference that we pass into all of those layers. In truth, since Azure Function do NOT support DI we manually build our container and pass it into our lower levels.

The catch to this approach though is, you do NOT want to write your dependency registration twice. To that end, I wrote an extension method called RegisterDependencies so I wouldnt need to have duplicate code. Additionally, I had to manually register the IConfiguration facade (this is done for you in normal startup flow).

The final block here is adding the binding rule for our parameter level attribute so that, when the runtime sees the attribute it knows to invoke the create method on our binding provider. Here is the code for our attribute:


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

view raw

attribute.cs

hosted with ❤ by GitHub

The one thing extra here is the user of the Binding attribute to denote the attribute represents a binding that will be used in Azure Function.

Part 6: Change our Starting Host

So, if you have ever worked with Azure Functions v2.0 you are recommended to use the FunctionsStartup class. Supporting this is a means to register extensions in a declarative way, a way that I could not get to work though I suspect involves the steps listed here. Regardless, IFunctionsHostBuilder (the type passed to the Configure method when using the interface) does NOT have a way to register extensions from code. So what to do?

Well, it turns out you can change IFunctionsHostBuilder with IWebJobsStartup which is the old way of doing this and that will provide a method to register the extension provider – shown below:


[assembly: FunctionsStartup(typeof(UserApi.Startup))]
namespace UserApi
{
public class Startup : IWebJobsStartup
{
// public override void Configure(IFunctionsHostBuilder builder)
// {
// builder.Services.AddTransient<IDataProvider, MongoDataProvider>();
// }
public void Configure(IWebJobsBuilder builder)
{
builder.Services.RegisterDependencies();
builder.AddExtension<UserTokenExtensionProvider>();
}
}
}

view raw

startup.cs

hosted with ❤ by GitHub

Again, note the call to RegisterDependencies which unifies our registration so we do not have duplicate code. I have yet to notice any irregularities with using the web jobs approach vs the Function host yet, please comment if you see anything.

Part 7: Handling the Result of our Token Parse

So, Azure Functions do offer the FunctionExceptionFilterAttribute base class which enables a hook to respond to uncaught exceptions raised by functions. Unfortuantely, this hook does not enable you to alter the response so, even if you catch the relevant exception the response code is already written – it seems to be more for logging that handling.

So, the best I could come up with is each function has to be aware of how to intepert a failed parse response. Here is my complete function that shows this:


[FunctionName("GetCurrentUserFunction")]
public async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "current/user")]HttpRequestMessage eventData,
ILogger logger,
[UserToken]UserTokenResult userResult)
{
if (userResult.TokenState == TokenState.Valid)
{
var user = await _dataProvider.GetUserByUsername(userResult.Username);
if (user == null)
{
return new NotFoundResult();
}
return new OkObjectResult(user);
}
else if (userResult.TokenState == TokenState.Empty)
{
return new BadRequestResult();
}
else
{
return new UnauthorizedResult();
}
}

view raw

function_two.cs

hosted with ❤ by GitHub

You can see that we introduced an enum called TokenState that I pass from the underlying Value Provider. This is not an ideal approach since it means each developer writing a function must be aware of the various auth scenarios that can occur for their function. This leads to duplication and create error proned code. But, it is the best that I can find, this far.

Closing

So, honestly, disappointed in Microsoft. I feel like the Azure Functions are really designed to be used behind an API Management gateway to alleviate some of the checks but, the DI maturity is abhorid. I really do hope this is more the case of me missing something than this being the actual state, especially given the rising importance of serverless in modern architectures.

I know I showed a lot of code and I hope you had good takeways from this. Please leave comments and I will do my best to answer any questions. Or I would love to know if I missed something with the way to do this that makes it easier.

3 thoughts on “Authenticating JWT Tokens with Azure Functions

  1. You are not alone. I just did something like this in a function app and was wishing there was just a tad more Asp.Net core like functionality in functions.

    Everyone of my functions that require security have these 5 lines at the top. It looks like if I were to use Function Extensions as you did, I’d only eliminate the first line of code (call to RequireAnyClaimAsync).

    var functionSecurityResult = await FunctionSecurityService.RequireAnyClaimAsync(req, _claims);
    if (functionSecurityResult.StatusCode != HttpStatusCode.OK)
    {
    return req.CreateResponse(functionSecurityResult.StatusCode, new
    ResponseModel(functionSecurityResult.StatusCode));
    }

    Liked by 1 person

  2. Good post !
    I would add that I have a problem with duplicating all the security boilerplate into each function. So I managed to integrate it in the IValueProvider implementation. The most important part is to throw an Exception, which prevent function host to issue a 500 Internal server error. As an example:
    public async Task GetValueAsync()
    {
    var token = _request.GetJwtToken();
    if(string.IsNullOrWhitespace(token))
    {
    _request.HttpContext.Response.StatusCode = 400;
    await _request.HttpContext.Response.WriteAsync(“provide token”);
    thrown new Exception(“provide token”);
    } else if(token.IsExpired())
    {
    _request.HttpContext.Response.StatusCode = 401;
    await _request.HttpContext.Response.WriteAsync(“expired token”);
    thrown new Exception(“expired token”);
    }
    return token;
    }

    Like

Leave a comment