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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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, | |
}; | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[FunctionName("GetCurrentUserFunction")] | |
public async Task<IActionResult> Run( | |
[HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "current/user")]HttpRequestMessage eventData, | |
ILogger logger, | |
[UserToken]UserTokenResult userResult) | |
{ | |
} |
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[AttributeUsage(AttributeTargets.Parameter)] | |
[Binding] | |
public sealed class UserTokenAttribute : Attribute | |
{ | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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>(); | |
} | |
} | |
} |
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:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[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(); | |
} | |
} |
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.
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));
}
LikeLiked by 1 person
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;
}
LikeLike