Why does CsrfFilter run before the authentication filters, and what breaks if you place a state-changing custom filter before it?
answer
- CSRF token gates mutating verbs; safe methods exempt
- Reject forgery early, before auth and before side effects
- After SecurityContextHolderFilter (token is session-bound)
- Side-effecting custom filter BEFORE CsrfFilter = CSRF bypass
- Stateless bearer APIs commonly disable CSRF
basics
~20 sCsrfFilter runs early so a forged, state-changing request is rejected before any authentication or business logic acts on it. If a custom filter that mutates state runs before CsrfFilter, it executes on requests that CSRF validation would have blocked — reintroducing the CSRF vulnerability.
solid answer
~40 s`CsrfFilter` validates the anti-CSRF token on mutating requests (POST/PUT/PATCH/DELETE) and rejects mismatches with an `AccessDeniedException` before the request reaches authentication or your handlers. It runs after `SecurityContextHolderFilter` (so it can associate the token with the session/context) but before `UsernamePasswordAuthenticationFilter` and other auth filters, so a forged mutation is stopped cheaply and early. If you insert a custom filter that performs a state change (writes to DB, mutates session) *before* `CsrfFilter` via `addFilterBefore(myFilter, CsrfFilter.class)`, that side effect runs even on requests that fail CSRF validation — effectively bypassing CSRF protection for that action. The rule: place any filter with side effects on mutating requests **after** `CsrfFilter`, and place authentication filters after it too so CSRF is validated before credentials are processed.
code
java · 15 lines// WRONG: side effect runs even when CSRF validation would reject the request
http.addFilterBefore(new StateChangingFilter(), CsrfFilter.class);
// RIGHT: CsrfFilter validates first; side effect only runs on valid requests
http.addFilterAfter(new StateChangingFilter(), CsrfFilter.class);
// Stateless API chain: no ambient cookie auth -> CSRF filter removed deliberately
@Bean @Order(1)
SecurityFilterChain api(HttpSecurity http) throws Exception {
http.securityMatcher("/api/**")
.csrf(csrf -> csrf.disable())
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.authorizeHttpRequests(a -> a.anyRequest().authenticated());
return http.build();
}go deeper
Know CSRF is validated early and protects state-changing requests.
Explain that CsrfFilter runs before auth and rejects with 403 via ExceptionTranslationFilter.
Reason about custom-filter placement relative to CsrfFilter and the side-effect bypass risk.
Discuss when disabling CSRF is safe (stateless bearer auth) and the session-bound token / SS6 lazy-token implications.
## What CSRF protection is **Cross-Site Request Forgery (CSRF)** tricks a logged-in user's browser into sending a state-changing request to your site using the user's ambient credentials (session cookie). The defense is a **synchronizer/secret token**: the server issues a per-session (or per-request) token the attacker's site cannot read (same-origin policy), and requires it on every mutating request. `CsrfFilter` enforces this. ## How `CsrfFilter` behaves - It uses a `CsrfTokenRepository` (default `HttpSessionCsrfTokenRepository`; cookie-based `CookieCsrfTokenRepository` for SPAs) to load/generate the expected token. - It applies a `RequireCsrfProtectionMatcher` that **exempts safe methods** (GET, HEAD, TRACE, OPTIONS) and only enforces on mutating verbs. - On mismatch/missing token it throws `AccessDeniedException` (`InvalidCsrfTokenException` / `MissingCsrfTokenException`), which `ExceptionTranslationFilter` (downstream) turns into a 403. ## Why its position is early 1. **Before authentication filters**: reject a forgery before spending work authenticating and before credential-processing touches the request body. It also means a forged request never advances to mutate state. 2. **After `SecurityContextHolderFilter`**: the token is typically session-bound, and the filter needs the loaded context/session to compare tokens and to defer token loading. 3. **Fail closed early**: cheap rejection reduces attack surface and load. ## The custom-filter gotcha If you place a side-effecting filter before `CsrfFilter`: ```java http.addFilterBefore(mutatingFilter, CsrfFilter.class); // DANGER if it changes state ``` then on a CSRF-invalid request the pipeline is: `SecurityContextHolderFilter` -> `mutatingFilter` (side effect happens!) -> `CsrfFilter` (now rejects with 403). The rejection is too late — the state change already occurred. This silently defeats CSRF protection for that operation. Correct placement is `addFilterAfter(mutatingFilter, CsrfFilter.class)` (or later), so CSRF validation gates the side effect. ## Related ordering facts - **Stateless token APIs** commonly do `csrf(csrf -> csrf.disable())` because CSRF relies on ambient cookie auth; a `Authorization: Bearer` header sent by JS is not automatically attached by the browser to cross-site requests, so CSRF risk is lower. Disabling removes `CsrfFilter` from that chain entirely. - **Logout**: `LogoutFilter` is itself a state change and is CSRF-protected; it sits after `CsrfFilter`. - **Custom authentication filters** should go before `UsernamePasswordAuthenticationFilter` but still after `CsrfFilter` for form-based flows, so a forged login-adjacent mutation is caught. ## Quick checklist - Side effects on mutating verbs -> after `CsrfFilter`. - Reading the CSRF token to render a form -> ensure the token is materialized (SS6 defers loading; expose it via a `CsrfToken` request attribute). - Disabling CSRF -> only for genuinely stateless, non-cookie auth.
- Why is disabling CSRF often acceptable for a stateless JWT/bearer API but not for a cookie-session web app?CSRF exploits ambient credentials the browser attaches automatically — session cookies. A bearer token in an Authorization header is added by your JS, not auto-sent cross-site, so forgery can't reuse it. Cookie-session apps still rely on auto-sent cookies and thus need CSRF protection.
- Which safe HTTP methods does CsrfFilter exempt, and why?GET, HEAD, OPTIONS, and TRACE. They're expected to be side-effect-free (safe/idempotent reads), so forging them shouldn't change server state; enforcing tokens on them would break normal navigation.
saying these in an interview costs you the question
- Placing a state-changing custom filter before CsrfFilter.
- Believing CSRF applies to GET requests.
- Disabling CSRF on a cookie-session web app 'to fix 403s' without understanding the exposure.
- Thinking CsrfFilter authenticates the user (it only validates the token).