Azure Functions Security and Authentication
Securing Azure Functions means controlling who can call the function and what the function is allowed to access. A function that is publicly exposed without proper security can be abused, overloaded, or used to steal data. This topic covers the key security layers available in Azure Functions.
Security Layers in Azure Functions
┌──────────────────────────────────────────────────────────────┐ │ SECURITY LAYERS │ │ │ │ Layer 1: Function Keys (API Keys) │ │ └── Basic protection. Caller must include a key. │ │ │ │ Layer 2: Azure Active Directory (AAD) Authentication │ │ └── Identity-based. Caller must prove who they are. │ │ │ │ Layer 3: Network Restrictions │ │ └── Allow only specific IP addresses or VNets. │ │ │ │ Layer 4: Managed Identity │ │ └── Function authenticates to other Azure services │ │ without storing passwords anywhere. │ │ │ │ Layer 5: HTTPS Only │ │ └── Block all HTTP traffic, allow only encrypted HTTPS. │ └──────────────────────────────────────────────────────────────┘
Layer 1 – Function Keys (API Keys)
Function keys are simple secret strings that a caller must include in every request. Azure Functions generates these keys automatically.
Key Types
| Key Type | Scope | When to Use |
|---|---|---|
| Function Key | One specific function only | Give different callers access to specific functions |
| Host Key | All functions in the app | Give one caller access to all functions in the app |
| Master Key | All functions + admin APIs | Admin operations only. Never share externally. |
Sending the Key in a Request
# Option 1: Query string parameter GET https://myapp.azurewebsites.net/api/GetOrder?code=abc123key&orderId=5 # Option 2: Request header (more secure — key not visible in logs) GET https://myapp.azurewebsites.net/api/GetOrder?orderId=5 x-functions-key: abc123key
authLevel Values in function.json
| authLevel | Key Required? | Which Key |
|---|---|---|
| anonymous | No | None |
| function | Yes | Function key or host key or master key |
| admin | Yes | Master key only |
Layer 2 – Azure Active Directory Authentication
Function keys provide basic protection but do not verify identity. Azure Active Directory (AAD) — now called Microsoft Entra ID — provides identity-based authentication. The caller must log in with a valid Azure account and receive a token before calling the function.
┌──────────────────────────────────────────────────────────────┐ │ AAD AUTHENTICATION FLOW │ │ │ │ 1. Caller logs in with Azure AD account │ │ │ │ │ ▼ │ │ 2. Azure AD validates credentials │ │ │ │ │ ▼ │ │ 3. Azure AD issues a JWT token (valid for ~1 hour) │ │ │ │ │ ▼ │ │ 4. Caller sends request with token in Authorization header │ │ Authorization: Bearer eyJhbGciOiJ... │ │ │ │ │ ▼ │ │ 5. Azure Functions validates the token │ │ │ │ │ ├── Valid token → Function runs ✓ │ │ └── Invalid / expired token → 401 Unauthorized ✗ │ └──────────────────────────────────────────────────────────────┘
Enabling AAD Authentication
Enable authentication through Azure Portal → Function App → Authentication → Add Provider → Microsoft.
After enabling, functions automatically reject requests without a valid Azure AD token. No code change is required in the function itself.
Reading User Identity in Function Code
module.exports = async function (context, req) {
// Azure injects the caller's identity after successful authentication
const user = req.headers["x-ms-client-principal"];
if (user) {
// Decode the base64-encoded identity
const decoded = Buffer.from(user, "base64").toString();
const identity = JSON.parse(decoded);
context.log(`Called by: ${identity.userDetails}`);
context.log(`User roles: ${identity.userRoles}`);
}
context.res = {
status: 200,
body: { message: "Authenticated successfully" }
};
};
Layer 3 – Network Restrictions
Network restrictions limit which IP addresses or virtual networks can reach the function app. All other IPs are blocked at the network level before any code runs.
IP-Based Restriction
┌──────────────────────────────────────────────────────────────┐ │ IP RESTRICTION │ │ │ │ Allowed IPs: │ │ ├── 203.0.113.10 (Office IP) → Allowed ✓ │ │ ├── 10.0.0.0/24 (VPN range) → Allowed ✓ │ │ └── All others → Blocked ✗ │ │ │ │ Set in: Azure Portal → Function App → │ │ Networking → Access Restrictions │ └──────────────────────────────────────────────────────────────┘
Layer 4 – Managed Identity
Managed Identity solves a common security problem: how does a function authenticate to another Azure service (like a database or Key Vault) without storing a password?
With Managed Identity, Azure automatically provides the function with an identity. The function uses this identity to request access tokens. No passwords, no connection strings with credentials.
┌──────────────────────────────────────────────────────────────┐ │ MANAGED IDENTITY FLOW │ │ │ │ Without Managed Identity: │ │ Function → Stores DB_PASSWORD in settings → Connects to DB │ │ Risk: Password can be leaked or stolen │ │ │ │ With Managed Identity: │ │ Function → "I am MyFunctionApp" → Azure AD verifies │ │ Azure AD → Issues token → Function uses token │ │ No password stored anywhere │ │ │ │ Steps: │ │ 1. Enable System-Assigned Managed Identity on Function App │ │ 2. Grant the identity access to the target service │ │ 3. Function code requests a token and uses it │ └──────────────────────────────────────────────────────────────┘
Enabling Managed Identity – Azure CLI
# Enable system-assigned managed identity az functionapp identity assign \ --name MyFunctionApp \ --resource-group MyResourceGroup # Output shows the principalId (the identity's object ID)
Using Managed Identity to Access Key Vault
const { DefaultAzureCredential } = require("@azure/identity");
const { SecretClient } = require("@azure/keyvault-secrets");
module.exports = async function (context, req) {
// DefaultAzureCredential uses Managed Identity automatically in Azure
// Falls back to local developer login during local development
const credential = new DefaultAzureCredential();
const vaultUrl = "https://mykeyvault.vault.azure.net/";
const client = new SecretClient(vaultUrl, credential);
// Get a secret from Key Vault — no password needed!
const secret = await client.getSecret("DatabasePassword");
context.log("Secret retrieved successfully");
// Use the secret value
const dbPassword = secret.value;
};
Layer 5 – HTTPS Only
Enable HTTPS-only mode to reject all unencrypted HTTP requests. This prevents credentials and data from being transmitted in plain text.
# Enable HTTPS-only via Azure CLI az functionapp update \ --name MyFunctionApp \ --resource-group MyResourceGroup \ --https-only true
CORS Security
Only allow specific origins to call the function from a browser. Open CORS (allowing all origins with *) means any website can call the function, which is a security risk for authenticated endpoints.
# Good: Allow only the specific frontend domain Allowed Origins: https://www.mystore.com # Bad: Allow everyone (OK for fully public APIs only) Allowed Origins: *
Security Checklist
| Check | Status |
|---|---|
| Use function or admin authLevel for sensitive endpoints | ✓ Must have |
| Enable AAD authentication for user-facing APIs | ✓ Recommended |
| Enable HTTPS-only mode | ✓ Must have |
| Store secrets in Key Vault, not App Settings | ✓ For production |
| Use Managed Identity to connect to Azure services | ✓ Best practice |
| Restrict CORS to known origins | ✓ For authenticated APIs |
| Never log sensitive data (passwords, tokens, keys) | ✓ Always |
| Add local.settings.json to .gitignore | ✓ Day one requirement |
