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:

/
Parser
	.Setup<bool>("security")
	.WithDescription("Enable securing HTTP functions using available providers")
	.SetDefault(false)
	.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;
			return;
		}
	}

	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.

How to integrate Azure Functions, GIPHY and Microsoft Teams?

Today I have pretty funny topic to show you all - we'll try to create a simple Azure Function, which will... find a random GIF image and post in on a channel in Microsoft Teams! Why such article? Well, we can't always do serious things :)

To bo honest, such triangle shows easily how to achieve integration on a corporate level using minimal resources. 

Setup

To start you have to have a Microsoft Teams channel created. I won't go into details of creating one(since it's pretty basic stuff) and just assume, you already have one. What we're interested in are the Connectors available:

When you click on the menu item, you'll see a window with many available connectors. We're searching for a particular one named Incoming Webhook:

When you click Configure, you'll see a simple wizard where you can insert a name of a WebHook and select an image. Once you click Create, a webhook URL will be provided so copy and save it. It'll look like this:

https://outlook.office.com/webhook/.../IncomingWebhook/.../...

Now we have to create a function.

Function

I decided to create a function, which will be triggered from Monday to Friday at 9:30 AM. To do so selected TimerTrigger with the following signature:

[TimerTrigger("0 30 9 * * 1-5")]TimerInfo myTimer

The whole code looks like this:

/
public static class GiphyTrigger
{
	private const string WebhookUrl = "https://outlook.office.com/webhook/.../IncomingWebhook/.../...";

	private static Lazy<HttpClient> HttpClient = new Lazy<HttpClient>(() => new HttpClient());

	[FunctionName("GiphyTrigger")]
	public static async Task Run([TimerTrigger("0 30 9 * * 1-5")]TimerInfo myTimer, TraceWriter log)
	{
		log.Info($"C# Timer trigger function executed at: {DateTime.Now}");

		var randomGif = await HttpClient.Value.GetAsync("https://api.giphy.com/v1/gifs/random?api_key=...&tag=&rating=PG-13");
		var content = await randomGif.Content.ReadAsStringAsync();
		var model = JsonConvert.DeserializeObject<GiphyModel>(content);

		log.Info($"Sending GIF to Microsoft Teams");
		var result = await HttpClient.Value.PostAsync(WebhookUrl, new StringContent($"{{\"@type\": \"MessageCard\",\"@context\": \"http://schema.org/extensions\",\"summary\": \"This is GIF\",\"themeColor\": \"0075FF\",\"sections\": [{{\"startGroup\": true,\"title\": \"**GIPHY says:**\",\"text\": \"![Text]({model.Data.Url})\"}}]}}"));

		result.EnsureSuccessStatusCode();
		log.Info($"Result is {result.StatusCode}");
	}       
}

public sealed class GiphyModel 
{
		public GiphyDataModel Data {get;set;}
}

public class GiphyDataModel
{
	public string Title {get;set;}
	[JsonProperty("image_url")]
	public string Url {get;set;}
}

In general it's more or less generic code, which calls GIPHY API and obtains a random gif. One thing is important however - when calling a webhook, the body of a request is a valid Actionable message, which is a special schema used within Office 365. You can find more info here.

Result

When a function is triggered, you may see following result:

I named my webhook Squirrel Commando, and now it greets me everyday with a random GIF. Of course you can use Incoming Webhooks in Microsoft Teams for more serious tasks(like some reports, alerts or notifications) and integrate all using Azure Functions(with Consumption Plan it'll cost you almost nothing...) - with such generic functionality, only the sky is the limit.