Securing Azure Functions Solutions: A Practical Guide for .NET Developers and Azure Engineers
Azure Functions make it incredibly easy to build and deploy applications. You write a method, add a trigger, deploy the code, and within minutes you have a running endpoint that can process requests, messages, or events. That simplicity is one of the platform’s biggest strengths. It is also one of the reasons security is often overlooked.
A Function App is rarely an isolated component. It typically reads secrets from Key Vault, connects to databases, accesses storage accounts, consumes messages from queues, or calls downstream APIs. Because of that, securing Azure Functions is not just about protecting an endpoint. It is about securing every interaction the application has with other systems.
The useful way to think about a function’s security is to split it in two, because the platform really does treat these as separate problems:
Inbound Security:
Who is allowed to call the function, and how do we verify their identity?
Outbound Security
What resources can the function access, and how does it authenticate to those resources securely?
Before diving into the details, keep one important principle in mind:
Access keys help restrict access, but identity is what truly secures a solution.
This distinction explains many of the recommendations throughout the guide.
Part 1 : Outbound : How Azure Functions Authenticate to Other Resources
Most discussions about Azure Functions security start with HTTP endpoints and access keys. In practice, one of the biggest security risks often exists elsewhere: how the function authenticates to the services it depends on.
Your function probably touches some mix of Key Vault, Storage, Azure SQL, Service Bus, Event Hubs, or an external API. How it authenticates to each of those is where a lot of risk actually lives. How a Function Proves Its Own Identity?
1.1 The Default Habit: Connection Strings
The most common way to connect a Function App to another service is by using a connection string. You create one, paste it into app settings, read it at startup, connect. It works every time.
This approach is familiar and easy to implement. Unfortunately, it introduces several long-term security challenges. A connection string is effectively a credential. Anyone who obtains it can use the permissions associated with it.
Another challenge is lifecycle management. Connection strings do not automatically rotate. They remain valid until someone manually changes them. In many environments, credentials remain unchanged.
This is why modern Azure security guidance recommends moving away from secrets whenever possible.
1.2 Managed Identity: A managed identity gives the function app its own account in Microsoft Entra ID, and lets Azure deal with the password behind it.
When the function needs something: a secret, a database connection; It asks Entra for a short-lived token and presents that. The token expires on its own. There’s no standing secret sitting in your config waiting to leak.
This provides several benefits:
- No credentials stored in code/configuration
- Automatic credential rotation
- Reduced risk of credential leakage
- Centralized access management through Azure RBAC
1.2.1 Two Flavors: System-Assigned and User-Assigned
There are two kinds, and picking between them is mostly about ownership and reuse.
System-assigned is the simpler default. Use it when an app’s permissions belong to that app and nothing else.
User-assigned earns its keep when you have several apps that all need the same access. You grant the permissions once, to the shared identity, instead of repeating yourself for each app. It also helps with automation: because the identity exists independently, you can set up its permissions before the app is even deployed, rather than racing to wire them up after the app is already live.
1.2.2 Using Managed Identity in .NET
The Azure SDK provides DefaultAzureCredential, which automatically selects the appropriate authentication mechanism based on the environment.
var client = new SecretClient(
new Uri("https://my-vault.vault.azure.net/"),
new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("DbConnectionString");When running locally, it typically uses the developer’s credentials.
When running in Azure, it automatically uses the managed identity assigned to the Function App.
1.2.3 Identity-Based Connections for Triggers and Bindings
Many teams successfully adopt managed identities in application code but continue using connection strings in triggers and bindings. This creates an inconsistent security model.
Modern Azure Functions support identity-based connections for many triggers and bindings, including the storage the runtime itself depends on.
1.2.4 RBAC: Defining What the Function Can Access
Managed identity settles how the function authenticates. RBAC settles what it’s allowed to touch. You need both; proving identity means nothing if that identity can do everything.
Authentication without proper authorization can still result in excessive access.
For example, a Function App that only needs to read secrets from Key Vault should not receive broad administrative permissions.
Instead, it should receive the minimum permissions required to perform its task.
A common example is assigning the Key Vault Secrets User role rather than broader roles such as Contributor.
Part 2 : Inbound Security: Controlling Who Can Call Your Functions
So far, we have focused on how Azure Functions authenticate to other resources. That solves the outbound side of security.
The next question is equally important: Who is allowed to call the function in the first place?
For HTTP-triggered functions, this is where most security discussions begin. Unlike queue, blob, or Service Bus triggers, an HTTP endpoint can potentially be accessed by anyone who can reach its URL.
Protecting these endpoints requires multiple layers of security. No single setting is enough on its own. Instead, each layer reduces risk and helps prevent a mistake in one area from becoming a full security incident.
2.1 CORS: If a browser app on another domain calls your function, CORS decides which sites are allowed to make that call.
2.2 Restricting Network Access: Authentication determines who can access an application. Network controls determine who can reach it. Both are important.
Access Restrictions allow administrators to create allow and deny rules based on:
- IP addresses
- Virtual Networks
- Service endpoints
Without restrictions, the Function App can potentially be reached from anywhere on the internet.
Private Endpoints: Private Endpoints provide an even stronger level of isolation. The Function App receives a private IP address inside a virtual network.
2.3 Understanding Function Access Keys
Open the Azure Portal → your Function App. Two different menu items:
- “App keys” (under the app) → here you see:
_master— the master keydefault(and any you add) — these are your host keys
- Click into a specific function → “Function keys” → here you see:
default(and any you add) — these are that one function's function keys
Azure Functions provide built-in authorization through access keys.
Unless a function is configured as anonymous, callers must present a valid key before the request is processed.
There are four different types of keys.
Function keys are scoped to a single function, so a leak only exposes that one endpoint. Worth knowing: at the Function authorization level, a caller may present either that function's own key or a host key.
Host keys cover the whole app with one key. Convenient, but a leak now reaches everything.
The master key is the one to be careful with. It reaches every function and the runtime’s admin APIs- and it can’t be revoked. If it leaks, you can’t kill it; the best you can do is rotate everything else. So never embed it in a client app, never hand it to an outside party, and keep it out of source control.
System keys are the platform’s own, extensions like Event Grid and Durable Functions use them internally. You rarely deal with them directly.
Managing Multiple Function Keys: Instead of sharing a single default key across every consumer, create separate keys for each integration. This provides several benefits: Easier auditing, Faster incident response
For example mobile-app, partner-api, reporting-job, integration-service
2.3.1 Sending a Key
A caller can pass a key two ways.
In the URL:
https://myapp.azurewebsites.net/api/orders?code=<KEY>Or in a header:
x-functions-key: <KEY>Both are accepted, but prefer the header. Keys in the query string have a habit of showing up in server logs, proxy logs, and browser history, which is the last place you want a live credential when you’re reading logs during an incident.
2.4 Authorization Level
You set the requirement right on the trigger:
[Function("Orders")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "get")] HttpRequest request)
{
return new OkObjectResult("Welcome to Azure Functions!");
}he authorization level determines what Azure Functions expects before processing a request.
The most commonly used options are:
- Anonymous
- Function
- Admin
Each serves a different purpose.
When Should You Use Anonymous?
The name “Anonymous” often causes confusion.
Many developers assume that anonymous access automatically means an insecure endpoint.
That is not always true.
The important question is:
Where is authentication happening?
If the Function App sits behind another authentication layer such as:
- App Service Authentication (Easy Auth)
- Azure API Management
- An API Gateway
- A Reverse Proxy
then authentication may already have occurred before the request reaches the Function App.
In those cases, configuring the trigger as: AuthorizationLevel.Anonymous
is often the correct choice.
The upstream component performs authentication and authorization, while the Function App focuses only on business logic.
Get Compile & Conquer’s stories in your inbox
Join Medium for free to get updates from this writer.
This avoids maintaining multiple overlapping authentication mechanisms.
When Should You Use Function Authorization?
Function authorization is often a practical choice for:
- Internal APIs
- Development environments
- Service-to-service communication
- Low-risk workloads
In these situations, a function key may provide a reasonable balance between simplicity and security.
However, it is important to understand its limitations.
A function key is a shared secret.
It can answer: Does the caller know the key?
It cannot answer: Who is the caller?
Because there is no identity attached to the request, auditing and fine-grained authorization become difficult.
For production-facing applications, additional authentication is usually required.
Admin Authorization
The Admin authorization level requires the master key.
Because the master key provides elevated privileges, this level is rarely used for normal application traffic.
It is primarily intended for runtime administration and operational scenarios.
Most customer-facing endpoints should never require Admin access.
2.4 Moving Beyond Keys: Authenticating Real Users and Applications
Function keys are useful, but they have limitations. They do not identify a user, service, or application.
Option 1: App Service Authentication (Easy Auth)
Easy Auth is built directly into Azure App Service and Azure Functions.
It can authenticate users through providers such as:
- Microsoft Entra ID
- GitHub
- Apple
- OpenID Connect providers
Easy Auth runs before requests reach the Function App.
If authentication fails, the request never reaches the function. This provides a simple way to add strong authentication without implementing token validation manually.
Option 2: Authentication in Application Code
Some applications require full control over authentication behavior.
In these situations, authentication can be implemented directly within the Function App.
A common approach is validating JWT tokens using Microsoft Authentication Library (MSAL).
This approach provides maximum flexibility but also places responsibility on the development team.
The team must ensure token validation remains correct, secure, and up to date.
For most scenarios, platform-managed authentication is easier to maintain.
Option 3: Azure API Management (APIM)
Azure API Management can sit in front of a Function App and validate every request before forwarding it.
This creates a centralized security layer.
APIM can perform:
- JWT validation
- IP filtering
- Rate limiting
- Request transformation
- Client certificate validation
This approach is particularly useful when many APIs share the same authentication requirements.
Instead of implementing security in every application, it can be managed centrally through APIM policies.
For highly secure environments, APIM can become the only public entry point into the Function App.
Non-HTTP Triggers
For non-HTTP triggers (such as Service Bus, Queue, Blob, Event Grid, and Timer triggers), inbound HTTP security controls do not apply. This means CORS, function keys, Easy Auth, and API Management are not relevant.
Security primarily focuses on resource access. Use Managed Identity, identity-based connections, and least-privilege RBAC to secure outbound access.
Part 3: Webhooks and Operational Hardening
The next step is making sure the application remains secure after it has been deployed.
A secure authentication design can still be undermined by weak operational practices. Exposed management endpoints, insecure deployment methods, missing monitoring, and poor input validation are all common causes of security incidents.
This section focuses on those operational concerns.
3.1 Securing Webhooks in Azure Functions
Webhooks are a common integration pattern in modern cloud applications.
Services such as:
- GitHub
- Stripe
- Twilio
- Slack
- Microsoft Graph
- Azure DevOps
can send HTTP requests to an Azure Function whenever an event occurs.
From a security perspective, a webhook is simply an HTTP endpoint that accepts requests from an external system.
That means the same security principles used for any HTTP-triggered Function also apply to webhooks.
Understanding Webhook Support in Azure Functions:
Azure Functions Runtime 1.x included dedicated webhook templates for scenarios such as:
- GitHub Webhooks
- Slack Webhooks
- Generic JSON Webhooks
Starting with Runtime 2.x, this dedicated webhook model was removed.
Today, webhooks are implemented using standard HTTP-triggered functions.
For modern Azure Functions applications, a webhook endpoint typically looks like this:
public class WebHookFunction
{
private readonly ILogger<WebHookFunction> _logger;
public WebHookFunction(ILogger<WebHookFunction> logger)
{
_logger = logger;
}
[Function("MyWebhook")]
public IActionResult Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
_logger.LogInformation("Webhook received.");
return new OkObjectResult("Success");
}
}From Azure Functions’ perspective, there is nothing special about a webhook. It is simply an HTTP endpoint receiving requests from another system.
Protecting Webhook Endpoints:
Many webhook providers, such as GitHub, Stripe, Slack, and Twilio, support request signing to verify the authenticity of incoming requests.
A common misconception is that a webhook URL is secure simply because it is difficult to guess. In reality, anyone who discovers the URL can attempt to send requests to it.
To prevent spoofed requests, the receiving Function App should validate the provider’s signature. The typical process is:
- The provider sends a request with a signature header.
- The Function App generates its own signature using the shared secret.
- The two signatures are compared.
- Requests with invalid signatures are rejected.
By verifying signatures, the Function App can confirm that requests originate from the expected provider and have not been tampered with. For public-facing webhook endpoints, signature validation should be considered a mandatory security control.
There’s no single switch that secures a function. It’s layers, each catching what the others miss: managed identity removes the credential, RBAC caps the damage, real authentication identifies the caller, the network shrinks who can even reach you, and monitoring tells you when something’s off.
If you remember one ordering, make it this: identity first, permissions second, network exposure third. Build in that order and you’ll end up with functions that are easier to reason about, easier to audit, and a lot harder to knock over in production.
Start with HTTPS and TLS
Before implementing authentication or authorization, ensure all communication with the Function App is encrypted. Enable HTTPS Only to force secure connections and configure a minimum TLS version of 1.2 or higher to block outdated and potentially vulnerable protocols.
These simple settings provide the foundation for application security. Without HTTPS, credentials, tokens, and sensitive data can be exposed in transit, reducing the effectiveness of higher-level security controls.
Secure Deployment Practices
Application security is not only about runtime behavior. Deployment pipelines and operational processes are equally important. An application with strong authentication controls can still be compromised if deployment surfaces remain exposed. Deployments should use automated CI/CD pipelines instead
Avoid FTP Deployments: In most environments, disabling FTP entirely is the preferred option.
Secure the Kudu (SCM) Endpoint: Every Function App includes a Kudu management endpoint (https://<app-name>.scm.azurewebsites.net) that provides powerful capabilities such as file access, deployments, diagnostics, and debugging.
Because these capabilities are highly privileged, the SCM endpoint can be an attractive target for attackers. When implementing network isolation and access controls, secure both the application endpoint and the Kudu endpoint. Protecting only the application endpoint leaves an important management surface exposed.
Disable Unnecessary Administrative Endpoints: Azure Functions expose administrative endpoints under /admin for runtime management and diagnostics. While useful for operational tasks, they may not be required for every application.
Where possible, restrict or disable unused administrative functionality. Minimizing exposed management interfaces helps reduce the overall attack surface and improves security.
Disable Remote Debugging: Remote debugging is valuable for development and troubleshooting but should not remain enabled in production. Leaving it active creates an additional entry point that can increase security risk.
Enable remote debugging only when required and disable it as soon as troubleshooting is complete. This aligns with the principle of minimizing exposed services and reducing the attack surface.
Enable Microsoft Defender for Cloud: Microsoft Defender for Cloud continuously assesses Azure resources for security risks and misconfigurations. It helps identify issues such as weak configurations, missing protections, exposure risks, and other security recommendations.
By providing continuous security monitoring and visibility, Defender for Cloud can help detect and address potential issues before they become incidents.
Microsoft Sentinel: Microsoft Sentinel extends Log Analytics with advanced security capabilities, including security monitoring, threat detection, incident investigation, and automated response workflows.
It is especially valuable in enterprise environments where security events from multiple applications and services need to be monitored and managed from a central location.
Reference Architecture :









