This check flags Azure Function HTTP triggers set to anonymous authorization, which lets anyone on the internet invoke your function without a key or token. Switch the auth level to function or admin, or front the function with proper identity-based auth, to close the gap.
HTTP-triggered Azure Functions are everywhere: webhooks, lightweight APIs, glue code between services, scheduled jobs that someone exposed over HTTP for convenience. They are quick to ship, and that speed is exactly where this misconfiguration sneaks in. When the authorization level is left at anonymous, the function endpoint accepts requests from anyone who knows or guesses the URL. No key, no token, no questions asked.
The Lensix check appservice_functionnoauthlevel looks at your Function Apps and reports any HTTP trigger configured with anonymous authorization so you can decide whether that exposure is intentional or an accident waiting to be abused.
What this check detects
Azure Functions HTTP triggers have an authLevel property that controls how callers authenticate to the function. It accepts three values:
- anonymous — no key required. Anyone with the URL can invoke the function.
- function — the caller must supply a function-specific or host key.
- admin — the caller must supply the master (host) key, which grants access to all functions in the app.
This check fires when it finds an HTTP trigger with authLevel set to anonymous. That value lives in the function's binding configuration, typically in function.json for script-based functions or in the trigger attribute for compiled languages like C#.
Note: Function keys are not a strong authentication mechanism on their own. They are shared secrets passed in the x-functions-key header or a code query parameter. They raise the bar above "anyone with the URL" but should not be treated as a substitute for real identity-based auth on sensitive endpoints.
Why it matters
An anonymous HTTP trigger is a public endpoint, full stop. Function URLs are not secret. They show up in browser history, proxy logs, client-side JavaScript, CI logs, Slack messages, and error reports. Once the URL leaks, an anonymous function is open to the world.
Here is what that looks like in practice:
- Data exposure. A function that returns customer records, internal config, or report data will hand it to anyone who calls it. Plenty of breaches start with an "internal" API that was never actually locked down.
- Unauthenticated writes. If the function writes to a database, queue, or storage account, an attacker can inject, corrupt, or flood your data with no credentials at all.
- Abuse and cost. Consumption plan functions bill per execution. An attacker (or a misbehaving bot) can hammer an anonymous endpoint and run up your bill or exhaust downstream service quotas. This is a denial-of-wallet attack.
- Pivot point. A function with a managed identity that can reach Key Vault, storage, or other resources becomes a stepping stone into the rest of your subscription if it can be invoked freely.
Warning: "It's behind an obscure URL" is not access control. Endpoint enumeration tools and leaked logs make obscurity worthless. Treat every anonymous function as if its URL is already public.
There are legitimate uses for anonymous triggers, for example a public webhook receiver that validates a provider signature inside the function body, or an endpoint sitting behind Azure API Management or a WAF that handles auth upstream. The point of this check is to make those decisions deliberate rather than accidental.
How to fix it
The fix depends on what the endpoint is for. In most cases you want to move from anonymous to function level, or layer real authentication on top.
Option 1: Set the auth level in code
For script-based functions (JavaScript, Python, PowerShell), edit the trigger binding in function.json:
{
"bindings": [
{
"authLevel": "function",
"type": "httpTrigger",
"direction": "in",
"name": "req",
"methods": ["get", "post"]
},
{
"type": "http",
"direction": "out",
"name": "res"
}
]
}
For C# functions using attributes, change the AuthorizationLevel in the trigger:
[Function("GetReport")]
public HttpResponseData Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post")] HttpRequestData req)
{
// ...
}
Redeploy the function for the change to take effect. After deployment, callers must include a key:
curl "https://myfuncapp.azurewebsites.net/api/GetReport?code=<function-key>"
Option 2: Enable App Service Authentication (Easy Auth)
For anything user-facing, key auth is the wrong tool. Use App Service Authentication to require an identity provider such as Microsoft Entra ID. This puts auth in front of the function before your code ever runs.
az webapp auth update \
--name myfuncapp \
--resource-group my-rg \
--enabled true \
--action LoginWithAzureActiveDirectory \
--aad-allowed-token-audiences "https://myfuncapp.azurewebsites.net" \
--aad-client-id <app-registration-client-id>
With Easy Auth enabled and set to require authentication, unauthenticated requests get a 401 before reaching your function, regardless of the trigger's authLevel.
Tip: Combine the two layers. Set authLevel to function as a defense-in-depth backstop and put Entra ID auth in front. If one control is misconfigured during a deploy, the other still holds.
Option 3: Fix it in Bicep / Terraform
If your functions are deployed declaratively, change the auth in the IaC, not in the portal, so the next deploy doesn't revert it. With Terraform, enabling authentication on the Function App:
resource "azurerm_linux_function_app" "example" {
name = "myfuncapp"
resource_group_name = azurerm_resource_group.example.name
location = azurerm_resource_group.example.location
auth_settings_v2 {
auth_enabled = true
require_authentication = true
unauthenticated_action = "RedirectToLoginPage"
active_directory_v2 {
client_id = var.aad_client_id
tenant_auth_endpoint = "https://login.microsoftonline.com/${var.tenant_id}/v2.0"
}
login {}
}
# ... rest of config
}
Danger: Before tightening auth on a live endpoint, confirm what is calling it. Webhooks, mobile clients, and partner integrations may break the moment you require a key or token. Coordinate the change, update callers with the new credentials first, and roll out behind a maintenance window for anything in production.
How to prevent it from happening again
Catching this once is good. Making it impossible to ship again is better.
Scan IaC in CI/CD
Add a policy check to your pipeline that fails the build when a function trigger is set to anonymous. With Checkov, for example, you can gate Terraform plans before they ever reach Azure:
checkov -d . --framework terraform \
--check CKV_AZURE_56,CKV_AZURE_88
You can also grep your function definitions directly as a cheap pre-commit guard:
# fail if any function.json uses anonymous auth
grep -rn '"authLevel": "anonymous"' . && \
echo "Anonymous function trigger found" && exit 1
Enforce with Azure Policy
Use Azure Policy to require authentication on Function Apps across a subscription or management group. The built-in policy "App Service apps should have authentication enabled" can be assigned with a Deny or Audit effect:
az policy assignment create \
--name "require-functionapp-auth" \
--display-name "Require authentication on Function Apps" \
--policy "95bccee9-a7f8-4bec-9ee9-62c3473701fc" \
--scope "/subscriptions/<subscription-id>" \
--params '{ "effect": { "value": "Audit" } }'
Warning: Start policy enforcement in Audit mode and review the results before switching to Deny. Flipping straight to Deny on a busy subscription can block legitimate deployments and create a flood of failures for teams who weren't warned.
Monitor continuously
IaC scanning only catches what flows through your pipeline. Resources still get tweaked by hand in the portal, and drift happens. Continuous posture monitoring with Lensix re-runs appservice_functionnoauthlevel across your subscriptions so an anonymous trigger added outside the pipeline gets flagged within the next scan rather than discovered during an incident.
Best practices
- Default to
functionlevel. Make non-anonymous the norm in your templates and starter projects so developers have to opt in to public access, not opt out of it. - Use identity, not keys, for anything sensitive. Function keys are shared secrets that are hard to rotate and easy to leak. Reserve them for low-risk internal calls and use Entra ID for real authorization.
- Front public APIs with API Management or a WAF. If you genuinely need a public endpoint, put a gateway in front for rate limiting, IP filtering, and request validation rather than exposing the function directly.
- Validate webhook signatures in code. For anonymous webhook receivers, verify the provider's HMAC signature before doing any work. An anonymous trigger that trusts its caller blindly is the worst of both worlds.
- Lock down networking too. Use access restrictions or private endpoints so the function is only reachable from networks you control. Auth and network controls reinforce each other.
- Rotate keys when they leak. If a function key ends up in a log or repo, rotate it immediately with
az functionapp keys setand update legitimate callers.
Anonymous HTTP triggers are not inherently wrong, but they should be a conscious architectural choice backed by other controls, never the result of a default left untouched. Audit what you have, tighten the endpoints that don't need to be public, and put a gate in your pipeline so the next anonymous trigger never reaches production unnoticed.

