Skip to content

CWE-352: Cross-Site Request Forgery (CSRF) - Java

Overview

CSRF vulnerabilities in Java web applications occur when state-changing endpoints don't verify that requests originated from the application itself. Spring Security enables CSRF protection by default, so a finding against a Spring application usually points at a configuration line that switched it off. Plain servlets and JAX-RS have no equivalent default, and need the check written by hand.

Primary Defence: Leave Spring Security's CSRF protection enabled, let the view layer emit the hidden _csrf field into forms, and keep state-changing operations on POST/PUT/DELETE so the CSRF filter validates them.

Common Vulnerable Patterns

Spring Boot with CSRF disabled

SecurityConfig.java
// VULNERABLE
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .authorizeRequests()
                .antMatchers("/admin/**").authenticated()
                .anyRequest().permitAll()
            .and()
            .csrf().disable();  // CSRF protection disabled!
    }
}
TransferController.java
// VULNERABLE
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/api")
public class TransferController {

    @PostMapping("/transfer")
    public ResponseEntity<String> transferFunds(
            @RequestParam String toAccount,
            @RequestParam BigDecimal amount,
            Principal principal) {

        // No CSRF validation - vulnerable to CSRF attacks
        transferService.transfer(principal.getName(), toAccount, amount);
        return ResponseEntity.ok("Transfer successful");
    }
}

Why this is vulnerable: With .csrf().disable() in the filter chain, nothing checks where a POST came from. A form on any other site can target /api/transfer, and the browser attaches the victim's session cookie to it, so the transfer runs as the victim. The only thing the attacker needs is for a logged-in user to load their page.

Servlet without CSRF protection

TransferServlet.java
// VULNERABLE
import jakarta.servlet.http.*;
import java.io.IOException;

@WebServlet("/transfer")
public class TransferServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
            throws ServletException, IOException {

        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("userId") == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        // VULNERABLE - No CSRF token validation
        String toAccount = request.getParameter("toAccount");
        String amount = request.getParameter("amount");

        Long userId = (Long) session.getAttribute("userId");
        transferService.transfer(userId, toAccount, new BigDecimal(amount));

        response.getWriter().write("Transfer successful");
    }
}

Why this is vulnerable: The servlet checks that a session exists, which proves the user is logged in but says nothing about where the request came from. A form on an attacker's page that posts to /transfer travels with the victim's session cookie and passes that check, so the transfer completes while the victim is looking at the attacker's site.

JAX-RS without CSRF protection

AccountResource.java
// VULNERABLE
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.*;

@Path("/account")
public class AccountResource {

    @POST
    @Path("/update-email")
    @Produces(MediaType.APPLICATION_JSON)
    public Response updateEmail(
            @FormParam("email") String email,
            @Context HttpServletRequest request) {

        HttpSession session = request.getSession(false);
        if (session == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        // VULNERABLE - Relies only on session cookie
        Long userId = (Long) session.getAttribute("userId");
        userService.updateEmail(userId, email);

        return Response.ok().entity("{\"status\":\"updated\"}").build();
    }
}

Why this is vulnerable: The resource authenticates on the session cookie alone. @FormParam means it accepts an ordinary HTML form post, which any site can send cross-origin, so an attacker's page can submit to /account/update-email and change the address on the victim's account without the victim seeing the request.

Secure Patterns

Spring Boot with CSRF protection

SecurityConfig.java
// SECURE
import org.springframework.boot.web.servlet.server.CookieSameSiteSupplier;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.csrf.CookieCsrfTokenRepository;

@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/api/**").authenticated()
                .anyRequest().permitAll()
            )
            .csrf(csrf -> csrf
                // CSRF enabled by default, customize if needed
                .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                .ignoringRequestMatchers("/api/webhook")  // Only for webhooks with alternative auth
            )
            .sessionManagement(session -> session
                .sessionFixation().newSession()
            );

        return http.build();
    }

    // Configure SameSite cookies
    @Bean
    public CookieSameSiteSupplier applicationCookieSameSiteSupplier() {
        return CookieSameSiteSupplier.ofStrict();
    }
}
TransferController.java
// SECURE
import org.springframework.web.bind.annotation.*;
import org.springframework.security.core.Authentication;

@RestController
@RequestMapping("/api")
public class TransferController {

    private final TransferService transferService;

    public TransferController(TransferService transferService) {
        this.transferService = transferService;
    }

    @PostMapping("/transfer")
    public ResponseEntity<TransferResponse> transferFunds(
            @RequestBody TransferRequest request,
            Authentication authentication) {

        // CSRF token automatically validated by Spring Security
        String username = authentication.getName();

        // Additional validation
        if (request.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
            return ResponseEntity.badRequest().build();
        }

        TransferResponse response = transferService.transfer(
            username, 
            request.getToAccount(), 
            request.getAmount()
        );

        return ResponseEntity.ok(response);
    }
}
TransferRequest.java
import java.math.BigDecimal;

public class TransferRequest {
    private String toAccount;
    private BigDecimal amount;

    // Getters and setters
    public String getToAccount() { return toAccount; }
    public void setToAccount(String toAccount) { this.toAccount = toAccount; }
    public BigDecimal getAmount() { return amount; }
    public void setAmount(BigDecimal amount) { this.amount = amount; }
}

Why this works:

  • Enabled by default: Validates all state-changing methods (POST/PUT/DELETE/PATCH) automatically via CsrfFilter before controllers execute
  • Cookie-based tokens: CookieCsrfTokenRepository.withHttpOnlyFalse() stores tokens in XSRF-TOKEN cookie JavaScript can read; validates against X-XSRF-TOKEN header using constant-time comparison
  • Defense-in-depth: CookieSameSiteSupplier.ofStrict() adds SameSite=Strict to cookies, blocking cross-site transmission before validation
  • Zero-configuration controllers: @PostMapping inherits CSRF validation from the security filter chain, so there is nothing per-controller left to forget
  • Proper exceptions: ignoringRequestMatchers("/api/webhook") exempts one endpoint that authenticates another way (an HMAC signature) rather than the whole API; sessionFixation().newSession() prevents token capture

Thymeleaf form with CSRF token

transfer.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
    <title>Transfer Funds</title>
</head>
<body>
    <!-- CSRF token automatically included by Thymeleaf -->
    <form th:action="@{/api/transfer}" method="post">
        <input type="text" name="toAccount" required />
        <input type="number" name="amount" step="0.01" required />
        <button type="submit">Transfer</button>
    </form>
</body>
</html>

Why this works:

  • Automatic token injection: The th:action attribute puts a hidden _csrf input into the rendered form, so nobody has to remember to add one
  • Framework integration: The token comes from the HttpServletRequest attribute where CsrfFilter placed it, so the rendered form and the filter always agree
  • Zero-configuration: A form written with th:action is protected without further work, and one written with a plain action stands out in review
  • XSS protection: The token is rendered into the HTML and never handed to JavaScript, which is less flexible for SPAs - those need the cookie-and-header approach below

JavaScript fetch with CSRF token

csrf-utils.js
function getCsrfToken() {
    // Spring Security sets CSRF token in cookie when using CookieCsrfTokenRepository
    const name = 'XSRF-TOKEN';
    const cookies = document.cookie.split(';');

    for (let cookie of cookies) {
        const [cookieName, cookieValue] = cookie.trim().split('=');
        if (cookieName === name) {
            return decodeURIComponent(cookieValue);
        }
    }
    return null;
}

// Make API call with CSRF token
async function transferFunds(toAccount, amount) {
    const csrfToken = getCsrfToken();

    const response = await fetch('/api/transfer', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
            'X-XSRF-TOKEN': csrfToken  // Spring expects X-XSRF-TOKEN header
        },
        credentials: 'same-origin',
        body: JSON.stringify({
            toAccount: toAccount,
            amount: amount
        })
    });

    if (!response.ok) {
        throw new Error(`Transfer failed: ${response.status}`);
    }

    return await response.json();
}

Why this works:

  • Cookie-to-header pattern: Reads the token from the XSRF-TOKEN cookie that Spring's CookieCsrfTokenRepository set, and sends it back in the X-XSRF-TOKEN header
  • Same-origin protection: An attacker's page can neither read the victim's cookies nor set a custom header on a cross-site request, so it cannot reproduce that header
  • SPA architecture support: credentials: 'same-origin' sends auth cookies only for same-origin requests; the initial page load sets the token cookie for later API calls
  • Graceful failure: A missing or expired token returns 403 Forbidden, and the client reloads the page to pick up a fresh one
  • Cookie security: Requires httpOnly=false for JavaScript access, but Secure and SameSite attributes provide defense-in-depth

Manual CSRF implementation with Servlet Filter

CsrfFilter.java
// SECURE manual implementation
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;

public class CsrfFilter implements Filter {

    private static final String CSRF_TOKEN_ATTR = "CSRF_TOKEN";
    private static final String CSRF_HEADER = "X-CSRF-TOKEN";
    private static final SecureRandom secureRandom = new SecureRandom();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        HttpSession session = httpRequest.getSession();

        String method = httpRequest.getMethod();

        // Generate token for session if not exists
        if (session.getAttribute(CSRF_TOKEN_ATTR) == null) {
            String token = generateToken();
            session.setAttribute(CSRF_TOKEN_ATTR, token);
        }

        // Validate CSRF token for state-changing requests
        if ("POST".equals(method) || "PUT".equals(method) || 
            "DELETE".equals(method) || "PATCH".equals(method)) {

            String sessionToken = (String) session.getAttribute(CSRF_TOKEN_ATTR);
            String requestToken = httpRequest.getHeader(CSRF_HEADER);

            // Also check form parameter for traditional form submissions
            if (requestToken == null) {
                requestToken = httpRequest.getParameter("csrf_token");
            }

            if (sessionToken == null || requestToken == null ||
                !MessageDigest.isEqual(
                    sessionToken.getBytes(StandardCharsets.UTF_8),
                    requestToken.getBytes(StandardCharsets.UTF_8))) {
                httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, 
                    "CSRF token validation failed");
                return;
            }
        }

        chain.doFilter(request, response);
    }

    private String generateToken() {
        byte[] bytes = new byte[32];
        secureRandom.nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }

    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}
}
web.xml
<filter>
    <filter-name>CsrfFilter</filter-name>
    <filter-class>com.example.security.CsrfFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>CsrfFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
TransferServlet.java
// SECURE with manual CSRF
@WebServlet("/transfer")
public class TransferServlet extends HttpServlet {

    @Override
    protected void doPost(HttpServletRequest request, 
                         HttpServletResponse response) 
            throws ServletException, IOException {

        // CSRF validation already done by CsrfFilter

        HttpSession session = request.getSession(false);
        if (session == null || session.getAttribute("userId") == null) {
            response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
            return;
        }

        String toAccount = request.getParameter("toAccount");
        String amount = request.getParameter("amount");

        Long userId = (Long) session.getAttribute("userId");
        transferService.transfer(userId, toAccount, new BigDecimal(amount));

        response.setContentType("application/json");
        response.getWriter().write("{\"status\":\"success\"}");
    }

    @Override
    protected void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
            throws ServletException, IOException {

        // Provide CSRF token to client
        HttpSession session = request.getSession();
        String csrfToken = (String) session.getAttribute("CSRF_TOKEN");

        request.setAttribute("csrfToken", csrfToken);
        request.getRequestDispatcher("/WEB-INF/transfer.jsp").forward(request, response);
    }
}

Why this works:

  • Framework-agnostic: A servlet filter gives you the Synchronizer Token Pattern in a legacy Java EE application, or anywhere Spring Security is not in play
  • Cryptographic tokens: SecureRandom with 32 bytes (256 bits) makes the token unguessable; URL-safe Base64 keeps it intact in URLs, headers and form fields
  • Session binding: Storing the token in HttpSession ties it to one authenticated user, so it cannot be reused across users and dies with the session
  • Hybrid support: Validates both the X-CSRF-TOKEN header (AJAX) and the csrf_token form parameter (traditional forms)
  • Production note: Prefer Spring Security when available; if implementing manually, compare token bytes with MessageDigest.isEqual() and keep token lifetime/session binding explicit.

JAX-RS with CSRF protection

CsrfTokenFilter.java
import jakarta.annotation.Priority;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import jakarta.ws.rs.Priorities;
import jakarta.ws.rs.container.*;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.Provider;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;

@Provider
@Priority(Priorities.AUTHENTICATION)
public class CsrfTokenFilter implements ContainerRequestFilter {

    @Context
    private HttpServletRequest servletRequest;

    @Override
    public void filter(ContainerRequestContext requestContext) throws IOException {
        String method = requestContext.getMethod();

        // Only validate state-changing methods
        if (!"POST".equals(method) && !"PUT".equals(method) && 
            !"DELETE".equals(method) && !"PATCH".equals(method)) {
            return;
        }

        HttpSession session = servletRequest.getSession(false);
        if (session == null) {
            requestContext.abortWith(
                Response.status(Response.Status.UNAUTHORIZED).build()
            );
            return;
        }

        String sessionToken = (String) session.getAttribute("CSRF_TOKEN");
        String requestToken = requestContext.getHeaderString("X-CSRF-TOKEN");

        if (sessionToken == null || requestToken == null ||
            !MessageDigest.isEqual(
                sessionToken.getBytes(StandardCharsets.UTF_8),
                requestToken.getBytes(StandardCharsets.UTF_8))) {
            requestContext.abortWith(
                Response.status(Response.Status.FORBIDDEN)
                    .entity("{\"error\":\"CSRF token validation failed\"}")
                    .build()
            );
        }
    }
}
AccountResource.java
// SECURE
@Path("/account")
public class AccountResource {

    @Inject
    private UserService userService;

    @POST
    @Path("/update-email")
    @Produces(MediaType.APPLICATION_JSON)
    @Consumes(MediaType.APPLICATION_JSON)
    public Response updateEmail(
            EmailUpdateRequest request,
            @Context HttpServletRequest httpRequest) {

        // CSRF validation done by CsrfTokenFilter

        HttpSession session = httpRequest.getSession(false);
        if (session == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        Long userId = (Long) session.getAttribute("userId");
        userService.updateEmail(userId, request.getEmail());

        return Response.ok()
            .entity(new StatusResponse("updated"))
            .build();
    }

    @GET
    @Path("/csrf-token")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getCsrfToken(@Context HttpServletRequest httpRequest) {
        HttpSession session = httpRequest.getSession();
        String token = (String) session.getAttribute("CSRF_TOKEN");

        if (token == null) {
            token = generateCsrfToken();
            session.setAttribute("CSRF_TOKEN", token);
        }

        return Response.ok()
            .entity(new CsrfTokenResponse(token))
            .build();
    }

    private String generateCsrfToken() {
        byte[] bytes = new byte[32];
        new SecureRandom().nextBytes(bytes);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(bytes);
    }
}

Why this works:

  • Framework-level filter: @Provider registers the filter with JAX-RS, and @Priority(Priorities.AUTHENTICATION) runs it before any resource method
  • Session binding: Storing tokens in HttpSession inherits the container's session management, clustering and timeout behaviour
  • REST endpoint for tokens: The /csrf-token resource hands tokens to JavaScript clients, which is what an SPA needs since it renders no server-side form
  • Early request abort: requestContext.abortWith() returns 403 Forbidden on a validation failure, so the resource method never runs
  • Production note: Prefer Spring Security when available; manual filters should use MessageDigest.isEqual() for token comparison and explicit session/token lifetime handling.
CsrfDoubleSubmitFilter.java
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.SecureRandom;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;

public class CsrfDoubleSubmitFilter implements Filter {

    private static final String CSRF_COOKIE = "XSRF-TOKEN";
    private static final String CSRF_HEADER = "X-XSRF-TOKEN";
    private static final String SECRET_KEY = System.getenv("CSRF_SECRET");

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {

        HttpServletRequest httpRequest = (HttpServletRequest) request;
        HttpServletResponse httpResponse = (HttpServletResponse) response;

        String method = httpRequest.getMethod();

        // The token is bound to this session, so the session has to exist
        // before one can be issued or checked
        HttpSession session = httpRequest.getSession(false);
        String sessionId = (session == null) ? null : session.getId();

        // Generate and set CSRF cookie if not present
        Cookie[] cookies = httpRequest.getCookies();
        String cookieToken = getCookieValue(cookies, CSRF_COOKIE);

        if (cookieToken == null && sessionId != null) {
            cookieToken = generateSignedToken(sessionId);
            Cookie csrfCookie = new Cookie(CSRF_COOKIE, cookieToken);
            csrfCookie.setPath("/");
            csrfCookie.setSecure(true);
            csrfCookie.setHttpOnly(false);  // JavaScript needs to read
            csrfCookie.setAttribute("SameSite", "Strict");
            httpResponse.addCookie(csrfCookie);
        }

        // Validate for state-changing requests
        if ("POST".equals(method) || "PUT".equals(method) || 
            "DELETE".equals(method) || "PATCH".equals(method)) {

            String headerToken = httpRequest.getHeader(CSRF_HEADER);

            if (!validateTokens(cookieToken, headerToken, sessionId)) {
                httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, 
                    "CSRF validation failed");
                return;
            }
        }

        chain.doFilter(request, response);
    }

    private String generateSignedToken(String sessionId) {
        byte[] randomBytes = new byte[32];
        new SecureRandom().nextBytes(randomBytes);
        String token = Base64.getUrlEncoder().withoutPadding().encodeToString(randomBytes);
        String signature = sign(token, sessionId);
        return token + "." + signature;
    }

    // The HMAC covers the session as well as the token, so a signed token is
    // only valid for the session it was issued to. Signing the token alone
    // would let an attacker who can write a cookie on this domain - from a
    // sibling subdomain, a DNS takeover, or plaintext HTTP - replay a
    // legitimate token of their own against another user.
    private String sign(String value, String sessionId) {
        try {
            String message = sessionId.length() + "!" + sessionId + "!"
                           + value.length() + "!" + value;
            Mac mac = Mac.getInstance("HmacSHA256");
            SecretKeySpec secretKey = new SecretKeySpec(
                SECRET_KEY.getBytes(StandardCharsets.UTF_8), "HmacSHA256");
            mac.init(secretKey);
            byte[] hmac = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));
            return Base64.getUrlEncoder().withoutPadding().encodeToString(hmac);
        } catch (Exception e) {
            throw new RuntimeException("Failed to sign token", e);
        }
    }

    private boolean validateTokens(String cookieToken, String headerToken, String sessionId) {
        if (cookieToken == null || headerToken == null || sessionId == null) {
            return false;
        }

        // Verify cookie signature
        String[] parts = cookieToken.split("\\.");
        if (parts.length != 2) {
            return false;
        }

        String token = parts[0];
        String signature = parts[1];
        String expectedSignature = sign(token, sessionId);

        if (!MessageDigest.isEqual(
                signature.getBytes(StandardCharsets.UTF_8),
                expectedSignature.getBytes(StandardCharsets.UTF_8))) {
            return false;
        }

        // Verify header matches cookie token
        return MessageDigest.isEqual(
            token.getBytes(StandardCharsets.UTF_8),
            headerToken.getBytes(StandardCharsets.UTF_8));
    }

    private String getCookieValue(Cookie[] cookies, String name) {
        if (cookies != null) {
            for (Cookie cookie : cookies) {
                if (name.equals(cookie.getName())) {
                    return cookie.getValue();
                }
            }
        }
        return null;
    }

    @Override
    public void init(FilterConfig filterConfig) {}

    @Override
    public void destroy() {}
}

Why this works:

  • No server-side token store: Validation needs only the HMAC secret and the session id, so no node has to remember which tokens it issued. The session is still required, because the signature binds the token to it
  • HMAC signature security: SecureRandom generates 256-bit tokens signed with HMAC-SHA256 under CSRF_SECRET, so a token cannot be forged without the secret
  • Dual validation: Verifies the signature with MessageDigest.isEqual() (constant-time) and then matches the cookie against the header - an attacker can neither read the cookie (same-origin) nor set a custom header cross-site
  • Cookie security: httpOnly=false for JavaScript access, secure=true for HTTPS-only, SameSite=Strict for cross-site blocking
  • Header-only validation: This filter reads the token from X-XSRF-TOKEN alone, so a plain form post needs JavaScript to attach the header; the session-bound filter above also accepts a csrf_token form parameter
  • Tradeoffs: Requires secret management/rotation; tokens don't auto-expire on logout - add timestamps for expiration validation

Common Pitfalls

  • ignoringRequestMatchers() scoped too broadly: Using .csrf(csrf -> csrf.ignoringRequestMatchers("/api/**")) to unblock one webhook ends up exempting the entire API surface, not just the endpoint that genuinely uses an alternative authentication scheme.
  • Mixing web.xml and annotation-based servlet registration in legacy apps: A CSRF filter's <url-pattern> declared in web.xml may not match a servlet added purely via @WebServlet scanning if the mapping wasn't also updated, leaving the new endpoint outside the filter's coverage.
  • A blanket csrf().disable() for a token-authenticated API later gaining a cookie-based endpoint: Disabling CSRF is legitimate when authentication is a bearer token in a header (browsers don't auto-attach it, so CSRF doesn't apply). If a later feature adds cookie/session-based auth to the same Spring Security filter chain, the earlier blanket disable now also covers the new cookie-authenticated endpoints.

Additional Resources