Protecting HTTP-triggered Azure Functions

A while ago I wrote about Securing Azure Function with JWT tokens. Since that time a lot happened with Azure Functions so I revisited the topic and researched this again and wrote down the possibilities on how to protect your HTTP triggered Functions.

Authorization Keys

Authorization Keys are the simplest way to secure your functions. The key needs to be passed either via the query string (code) or with a HTTP header (x-functions-key) to the function and will be validated by Azure Function runtime/host. Authorization keys can be generated on function level within the portal or via the key management API and are just simple strings. There are two types of keys that can be defined:

  • Admin/Host keys; these keys are shared for all functions and can be used to trigger any function regardless of the configured level.
  • Function keys; these can be used to only trigger specific functions with the configured level function and where the key matches the configure key for that function.

Authorization keys are stateless and do not contain any data about the caller. Since they are static and you need to hand them over (by email, chat, etc.) to the caller they are not usable for public clients and are meant for machine to machine communication. You can find more information about the authorization keys in the documentation.

Conclusion:

  • Meant for machine to machine communication.
  • Not usable for public clients.
  • No information about the caller.
  • Key Management can be “hard” if you have a lot of clients and functions.
  • Be aware that at development time all Authorization Key checks are disabled and you can call every function.

Easy Auth / Azure App Service Authentication

Since a couple of months Azure App Service Authentication (also called EasyAuth) is now available for Azure Functions. With Easy Auth the authentication will be handled by Azure App Service it self and works basically in two ways (at least when configured with Azure AD, I haven’t tried other login providers).

  1. It acts as a client that redirects the user to the login provider to retrieve an id_token. This is useful in cases where the whole application (frontend and backend) is served by your function application (e.g. a Single-Page-Application that is stored in Azure Storage and will be served via Function Proxies). In this cases Easy Auth seems to care only about the id_token but not about the access_token.
  2. It acts as an authentication middleware. In this case Easy Auth will just validate incoming JWT bearer tokens send via HTTP Authorization header and grants access to your function app. This is useful when your Function App acts as the backend of a client application that runs somewhere else (e.g. Mobile App). Easy Auth does not work with references tokens but only with JWT tokens!

Easy Auth uses federated authentication and supports Azure AD, Microsoft Login, Google Login, Facebook Login and Twitter Login. Easy Auth is meant to authenticate (human) users but using it as an authentication middleware it can also be used to authentication clients. In order to get information about the current user you can use the ClaimsPrincipal bindings.

Easy Auth can be enabled within the Azure Portal in your Function application.

Azure Portal - Easy Auth

Conclusion:

  • Meant for human to machine communication, but can also be used for machine to machine communication.
  • Usable for public and private clients.
  • Contains information about the caller.
  • User Management is offloaded to other systems; lets you integrate Function Apps into existing IAM environments.
  • Since Easy Auth is an App Service feature it is not available when hosting your Function App in Docker nor is it available at development time!

Custom OpenId Connect provider

By accident I discovered that Easy Auth is using the OpenId Connect protocol to integrate Azure AD, so it is possible to use your own OpenId Connect provider/server like IdentityServer. Be aware that this is an implementation detail of Easy Auth and that this can be changed anytime so it may break! To change the provider go to Platform features -> Authentication / Authorization -> Azure Active Directory and switch the Management mode to Advanced. You can now change the Issuer Uri and Client ID to your own OpenId Connect compatible provider. In this example I use a demo instance of IdentityServer to authenticate users.

Azure Portal - Custom OpenID Connect provider

Allowed audiences in authentication middleware

If you are using Easy Auth as an authentication middleware make sure that the access token contains a audience that is configured in Easy Auth. You can change the allowed audience in Easy Auth by going to Platform features -> Authentication / Authorization -> Azure Active Directory and switch the Management mode to Advanced.

API Management

Azure API Management is a reverse proxy that sits in front of your Function App. It receives every requests and then passes it to your application. This allows you to analyze the request and reject it if it does not meet your requirements. API Management comes with predefined policies for access restriction and authentication. You can also authorize requests using an external authorizer or use policy expressions to create your own logic.

Since API Management is an extra service that sits in front of your Function App, with its own domain/url, you need to make sure that your Function App can not be called directly. Either use Function Keys (that are then added to the request by API Management) or use VNETs (with Azure Function Premium Plan) to make your Function App only reachable via API Management. Also, if you need data about the current user in your Function App you need to pass the data from API Management to your App (e.g. QueryString or addition HTTP Header).

Conclusion

  • Additional service with additional costs (free quota available).
  • Function App must still some how be protected separately.
  • Usable for public and private clients.
  • Information about the caller must be passed manually to the function app.
  • User Management is offloaded to other systems; lets you integrate Function Apps into existing IAM environments.

Roll your own / write code

As described in my previous post (Securing Azure Function with JWT tokens) it is of course possible to create code that runs with your functions to authenticate the user. Since the authetication code is part of your application it is the only solution that works in all environments (AppService, Docker, development time, etc) but it also come with caveats.

  • Your authentication code needs to be secure! While this is also true for all other authentication methods, it is usually more secure to rely on a battle-proven existing solution, instead of creating your own.
  • At the time your authentication code is running, all bindings of your Function already did run. This means you produce unnecessary load and depending on the binding the Function may even have executed something, that should not have been executed by current user (e.g. you have sql bindings and you execute a stored procedure that has side affects). You can mitigate this by using dynamic bindings.

Conclusion

  • Code must be secure (as it should be always!).
  • Be aware of binding execution!
  • Available everywhere.

Feedback

Please let me know (Twitter / Email) what you are thinking about protecting your HTTP triggered Azure Functions and the solutions or what you are using to protect your Azure Function App!