Clickjacking in Spring Boot with Api Keys
Clickjacking in Spring Boot with Api Keys — how this specific combination creates or exposes the vulnerability
Clickjacking is a client-side UI redressing attack where an attacker tricks a user into interacting with a hidden or disguised element inside an invisible or disguised frame. In a Spring Boot application that relies on API keys for authentication, embedding protected endpoints inside frames or iframes can expose key-based flows to clickjacking, especially when defenses are incomplete.
When an API key is used for authentication—typically passed via request headers or query parameters—a protected endpoint may still render a page that can be framed. If the response does not set appropriate frame-denial headers (e.g., X-Frame-Options or Content-Security-Policy: frame-ancestors), an attacker can load that page inside an invisible iframe. A user who is already authenticated (for example, via session cookies after a key-based login) may inadvertently trigger actions or reveal sensitive data when interacting with the attacker’s page, because the browser sends credentials automatically.
Consider a Spring Boot controller that accepts an API key as a header and returns an HTML page with embedded widgets or status panels. If that page is served with a permissive frame policy, an attacker can craft a page that overlays invisible controls on top of legitimate UI elements. For example:
@RestController
@RequestMapping("/api/report")
public class ReportController {
@GetMapping
public String generateReport(@RequestHeader("X-API-Key") String apiKey, Model model) {
// vulnerable: returns HTML that can be framed without frame-ancestors policy
model.addAttribute("data", fetchReportData(apiKey));
return "report"; // Thymeleaf template
}
}
If the report template does not instruct the browser to prevent framing, and the user’s browser includes the API-key-authenticated session, an attacker can overlay buttons or links on a fake page that submit requests to the real endpoint. This can lead to unauthorized actions or leakage of key-associated data, particularly when the API key is tied to elevated permissions or long-lived tokens.
Additionally, if the application exposes an unauthenticated endpoint that still reveals sensitive information and is missing frame-ancestor restrictions, an attacker can combine that with social engineering to make the user believe they are interacting with a legitimate widget while the request is being performed in the background. The combination of API-key-based auth and missing frame controls creates a scenario where trust is placed in headers and cookies that can be exploited within malicious contexts.
Api Keys-Specific Remediation in Spring Boot — concrete code fixes
Remediation focuses on preventing framing of authenticated responses and ensuring that API-key-requiring endpoints are not inadvertently embeddable. Two complementary headers provide strong defense in depth.
1. X-Frame-Options
Set a global filter or controller advice to add X-Frame-Options: DENY or X-Frame-Options: SAMEORIGIN. DENY is preferred for API-driven pages that should never be framed.
@Configuration
public class SecurityConfig {
@Bean
public FilterRegistrationBean xFrameOptionsFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
registration.setFilter(new XFrameOptionsFilter());
registration.addUrlPatterns("/api/*");
registration.addInitParameter("policy", "DENY");
return registration;
}
public class XFrameOptionsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setHeader("X-Frame-Options", "DENY");
filterChain.doFilter(request, response);
}
}
}
2. Content-Security-Policy frame-ancestors
For modern browsers, use Content-Security-Policy: frame-ancestors to explicitly control which origins can embed the page. For API-only responses, you can restrict framing to none.
@Configuration
public class SecurityConfig {
@Bean
public FilterRegistrationBean cspFrameAncestorsFilter() {
FilterRegistrationBean registration = new FilterRegistrationBean<>();
registration.setFilter(new CspFrameAncestorsFilter());
registration.addUrlPatterns("/api/*");
registration.addInitParameter("policy", "'none'");
return registration;
}
public class CspFrameAncestorsFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) throws ServletException, IOException {
response.setHeader("Content-Security-Policy", "frame-ancestors 'none'");
filterChain.doFilter(request, response);
}
}
}
If the application serves both HTML and API responses, scope the filters to endpoints that render pages or use a more granular expression. For endpoints that return JSON and are not intended for browser embedding, the headers still help prevent misuse in edge cases where responses are loaded in error pages.
Ensure that API keys are transmitted securely via headers (not query parameters where possible) and that responses requiring authentication are not served with permissive framing policies. Combining these headers with HTTPS and strict CORS rules further reduces risk.