Skip to content

CWE-306: Missing Authentication for Critical Function

Overview

Missing authentication occurs when a critical function, route, or handler has no identity check at all - not a flawed one. The usual cause is a path missed when authentication was wired up elsewhere: an internal admin API left open because it isn't linked from the UI, a debug endpoint shipped without auth, a new route that never inherited the shared authentication middleware, or a service-to-service API that trusts network location instead of verifying caller identity.

Relationship to Other CWEs

OWASP Classification

A07:2025 - Authentication Failures

Risk

Critical: A critical function with no authentication check is reachable by anyone who discovers or guesses its address - no credential theft, session hijacking, or logic flaw is required. Depending on what the function does, this can expose sensitive data, allow arbitrary state changes, or hand an attacker administrative capability directly.

Remediation Steps

Core Principle: Every code path reaching a critical function must require authentication by default rather than as a per-route opt-in - then enumerate the paths and confirm each one does.

Trace the Data Path

  • Source: Any inbound request, RPC call, message, or internal API invocation reaching application code
  • Sink: The critical function itself - a state change, a non-public data read, an administrative action, or a trust-boundary crossing
  • Missing Controls: No authentication check anywhere on the path from source to sink - the route was never registered under the shared authentication middleware, was added outside the standard routing group, or the caller's identity is assumed from network placement rather than verified

Enforce Authentication by Default (Primary Defense)

What removes the weakness is an authentication check on the path, applied in a way a new route cannot silently miss:

  • Attach the same authentication mechanism used elsewhere - session check, token validation, service credential verification - and prefer a guard applied by default over a per-route opt-in, so new routes inherit protection automatically. Where the framework offers it, make declining authentication the thing a route has to say out loud: @AllowAnonymous, an explicit public list, a deny-by-default filter chain. An opt-in scheme regenerates this finding every time someone adds a route
  • For service-to-service calls, verify caller identity through mTLS, signed tokens, or service credentials rather than trusting that the request originated from an internal network
  • Fail closed: if the authentication check cannot run or its configuration is missing, deny access rather than default to allow

Enumerate and Diff Against Coverage

The fix above closes the reported route, which is one sample rather than the whole population. This step finds the ones nobody reported:

  • List all routes, RPC methods, message handlers, and internal APIs, including debug, health-check, and internal-only endpoints
  • Compare that list against what the authentication middleware, gateway, or framework guard actually covers; treat anything not on the covered list as a candidate gap
  • For each uncovered path, confirm whether it performs a critical function - intentionally public endpoints are out of scope, but anything touching data access, state change, or administrative capability is not

Test Directly Against the Endpoint

  • Send unauthenticated requests straight to the route or handler, not just through the UI, and confirm the response is a rejection
  • Verify the fix did not exempt sibling routes registered the same way, and add the endpoint to any route inventory used to catch future gaps
  • Re-scan with the security scanner to confirm the finding is resolved

Common Vulnerable Patterns

// VULNERABLE - pseudo-code
route "/admin/deleteUser" -> deleteUserHandler
// no authentication middleware attached to this route

// Attack: send a request directly to /admin/deleteUser with no credentials
// Result: the handler executes with no identity check at all

Why this is vulnerable: the handler is correct; the defect lives in the routing table. Authentication is attached by convention - a middleware group, a decorator, a filter chain - and a route registered outside that convention silently does not get it, because nothing in the framework requires a route to declare whether it is protected. This is a weakness of absence: every line of deleteUserHandler is right, there is no dangerous call to search for, and a diff shows nothing wrong.

Two assumptions keep these routes open. The first is that nothing links to the route. That does not protect it: route names are recoverable from client-side bundles, source maps, an API schema, or a wordlist, and an unauthenticated endpoint is exactly what an automated scan looks for. The second is that the perimeter authenticates. That holds only for traffic that arrives through the perimeter; a request reaching the service directly, from a compromised neighbour or a misrouted internal caller, meets whatever the service itself enforces. Prefer a framework configuration that denies by default, so that adding a route without a decision fails closed rather than open.

Secure Patterns

// SECURE - pseudo-code
route "/admin/deleteUser" -> requireAuthentication -> deleteUserHandler
// authentication guard applied by the shared routing group, not opted into per-route

Why this works: The authentication check is attached through the same centralized mechanism every comparable route uses, so a new or overlooked route inherits protection automatically instead of depending on a developer remembering to add it. Testing the endpoint directly - not just through the UI - confirms the check actually runs on the code path an attacker would use.

Common Pitfalls

  • Relying on the UI not linking to the endpoint: Anyone who guesses the path, enumerates it, or finds it in client-side code or documentation reaches the route directly.
  • Authenticating at the API gateway only: Backend services trust any request a perimeter gateway or load balancer has passed through, so anything reachable without going through it - another internal service, a misconfigured route, a debug port - skips the check.
  • Adding a new route outside the shared routing group: A handler registered directly on the underlying web framework, rather than through the application's standard router, never picks up the middleware every other route gets by default.

Additional Resources