Secure Azure Functions locally using a custom provider

Developing Azure Functions could be cumbersome if you want to use App Service Authentication feature. While it works flawlessly when a function is deployed, it brings many unfair challenges when working locally(mostly because of a need to create an artificial mock of identity provider and injecting it somehow). I decided to give it a try and modify Azure Functions CLI a little bit, to it has that feature implemented already. Surprisingly it was easier that I thought.

How does Azure Functions CLI work?

When you're working with Functions locally, when you hit F5, you'll see a local runtime starting and ready to handle a request(or trigger a function). In fact VS starts it using Azure Functions CLI and invoking following command:

> func start

Local instance of runtime with a boilerplate function enabled

When this local host is started, it handles multiple features like:

  • loading settings from local.settings.json
  • starting a runtime
  • providing an endpoint to handle HTTP requests
  • ...and many more

In general you're able to pass many different parameters so you can start a host listening on a specific port, with HTTPS enabled and CORS configured. You can do it like so:

func start --useHttps=true --cors=*

I got an idea to extend parameters so you can do following:

func start --security=true

So each function invocation has to be challenged against a custom security provider. How did I achieve this?

Handling a parameter

The very first thing I had to do was to handle a security parameter in CLI. To do so I modified StartHostAction class which parses inline arguments when CLI is started. Added line looks like this:

	.WithDescription("Enable securing HTTP functions using available providers")
	.Callback(s => Security = s);

This was super easy. Let's do something more difficult - use this parameter so some logic is performed.

Securing each request

Because CLI is built against new ASP.NET Core pipeline, you have to provide a custom middleware so each request has to pass through it. There's a Startup class, which is the foundation of the whole host. There you can inject your functionality as I did:

app.Use(async (context, next) =>
	if (_security)
		var provider = new SecurityProvider();
		var authenticationResult = provider.IsAuthenticated(context.Request);

		if (authenticationResult == false)
			context.Response.StatusCode = 401;

	await next.Invoke();

Now if security is enabled, each request will be validated using some SecurityProvider,which is a custom class implementing following interface:

internal interface ISecurityProvider
	bool IsAuthenticated(HttpRequest req);

How does it work?

Now when I start CLI using following command:

func start --security=true

I'm getting the following result:

When I disable security:

Of course the error in the second response comes from the boilerplate function because I didn't pass name parameter.

Now since IsAuthenticated() has a full request passed, you can implement whichever flow you want, starting from a very basic one like me:

public class SecurityProvider : ISecurityProvider
	public bool IsAuthenticated(HttpRequest req)
		if (req.Headers.ContainsKey("Authorization"))
			return true;

		return false;

What's next?

In the next episode I'll try to enhance this solution a little bit, so ISecurityProvider will be loaded from a Function App you'll be developing locally(so it gains much flexibility). For now you can following this issue on GitHub, where I proposed solution I described above.