Summary
A Spring Boot application utilizing Auth0 OAuth2 Resource Server protection failed to allow unauthenticated access to /health and /actuator/health endpoints after deployment to Azure App Service. Despite explicit .permitAll() configuration in the SecurityFilterChain, the application returned 401 Unauthorized in the cloud environment, while functioning perfectly in local development.
Root Cause
The root cause is Path Prefixing/Rewriting or Proxy Header Manipulation performed by the Azure App Service infrastructure (or an associated Application Gateway/Front Door).
- Path Mismatch: Azure App Service often acts as a reverse proxy. If the platform or a configured rule rewrites the incoming request path (e.g., stripping a virtual directory prefix or adding one), the path seen by the Spring Boot application no longer matches the literal strings
/healthor/actuator/healthdefined in therequestMatchers. - Filter Chain Bypass Failure: When the path is modified by a proxy before it reaches the Servlet container, Spring Security’s
AntPathRequestMatcherfails to find a match for the “permitted” paths and falls through to the.anyRequest().authenticated()rule. - Security Filter Order vs. Proxy Logic: The 401 is triggered because the application treats the incoming request as a standard protected resource because the effective URI in the Spring Context does not align with the configured URI in the security policy.
Why This Happens in Real Systems
In local development, the application sits directly on the network interface. The URI you type in your browser is exactly what the Spring DispatcherServlet receives. In production:
- Reverse Proxies: Load balancers and ingress controllers (like those used in Azure) often modify headers like
X-Forwarded-Prefixor rewrite paths to route traffic to specific microservices. - Virtual Directories: Cloud platforms often host multiple apps under a single domain, requiring path-based routing that alters the request URI.
- Implicit Security Layers: Azure App Service might have “Easy Auth” (App Service Authentication) enabled at the platform level, which intercepts requests before they even reach the Spring Boot application.
Real-World Impact
- Broken Observability: Automated health checkers (Azure Monitor, Kubernetes Liveness probes) fail, causing the platform to mark the instance as unhealthy and trigger unnecessary restarts or “death spirals.”
- False Positives in Monitoring: High error rates in dashboards due to 401s on monitoring endpoints, masking actual application errors.
- Deployment Failures: Zero-downtime deployments (Blue/Green) may fail if the deployment controller cannot verify the health of the new slot.
Example or Code
To fix this, we must ensure Spring Boot is “Proxy Aware” so it uses the original URI for security matching, and we must use more robust path matching.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
// Ensure the application respects X-Forwarded-* headers
.apply(new ForwardedHeaderFilter())
.authorizeHttpRequests(auth -> auth
// Use more flexible pattern matching
.requestMatchers("/health/**", "/actuator/**").permitAll()
.requestMatchers("/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter()))
)
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
In application.yml:
server:
forward-headers-strategy: framework
How Senior Engineers Fix It
- Enable Forwarded Header Support: Configure
server.forward-headers-strategy=framework. This tells Spring to look atX-Forwarded-PrefixandX-Forwarded-Protoheaders to reconstruct the “true” request URI. - Implement Pattern Matching Robustness: Instead of exact string matches like
/health, use wildcard patterns like/health/**to account for potential sub-paths added by proxies. - Verify Infrastructure Headers: Use a tool like
curl -vor inspect Azure logs to see if the platform is injecting a prefix (e.g.,/myapp/actuator/health). - Audit Platform-Level Auth: Check if Azure App Service Authentication (Easy Auth) is enabled. If it is, the 401 is being thrown by Azure’s infrastructure, not Spring Boot, and must be bypassed via “Allow anonymous requests” in the Azure Portal settings.
Why Juniors Miss It
- The “Works on My Machine” Fallacy: Juniors often assume that if the code is identical, the behavior must be identical, ignoring the environmental context (the network topology).
- Focusing on Application Logic Only: They spend hours debugging the
SecurityFilterChainlogic or theAudienceValidatorbecause the error is a401, assuming the issue is with the JWT or the code, rather than the request routing. - Ignoring Middleware: They treat the application as an isolated island, forgetting that in production, the application is the last step in a long chain of proxies, load balancers, and gateways.