Spring Security 6(boot 3.2) downloading file via form in post request throws error null _csrf token

117 Views Asked by At

I just migrated my spring boot app to 3.2 from 2.7. Right now everything is working fine login, request which i make through axios as i am using vue js. But when i download file via form element using javascript(I am downloading pdf of a page using puppeteer) which is a post request it throws and error crsf token is null. I am unable to find what i am missing in security configuration.

{
"title":"Something Went Wrong",
"status":500,
"detail":"Invalid CSRF Token 'null' was found on the request parameter '_csrf' or header 'X-XSRF-TOKEN'."
}

My request payload look like this

values:{
"htmlContent":"some html content for which i want to generate pdf",
"config":{}
}
_csrf: crsf token
t: some integer value

My Javascript method to download file is

function downloadFile(url, method, data, transactionId) {
const form = createElement('FORM');
form.method = method;
form.action = url;
const input = createElement('INPUT');
input.id = 'values';
input.name = 'values';
input.type = 'hidden';
input.value = JSON.stringify(data);
form.appendChild(input);

const csrfInput = createElement('INPUT');
csrfInput.id = '_csrf';
csrfInput.name = '_csrf';
csrfInput.type = 'hidden';

debugger;
csrfInput.value = Cookies.get('XSRF-TOKEN');
form.appendChild(csrfInput);

if (transactionId) {
    const transactionIdInput = createElement('INPUT');
    transactionIdInput.id = 't';
    transactionIdInput.name = 't';
    transactionIdInput.type = 'hidden';
    transactionIdInput.value = transactionId;
    form.appendChild(transactionIdInput);
}

document.body.appendChild(form);
form.submit();
form.parentNode.removeChild(form);
}

Csrf Token Request Handler taken from spring docs

final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler {
    private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler();

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, Supplier<CsrfToken> csrfToken) {
        /*
         * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of
         * the CsrfToken when it is rendered in the response body.
         */
        this.delegate.handle(request, response, csrfToken);
    }

    @Override
    public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) {
        /*
         * If the request contains a request header, use CsrfTokenRequestAttributeHandler
         * to resolve the CsrfToken. This applies when a single-page application includes
         * the header value automatically, which was obtained via a cookie containing the
         * raw CsrfToken.
         */
        if (StringUtils.isNotBlank(request.getHeader(csrfToken.getHeaderName()))) {
            return super.resolveCsrfTokenValue(request, csrfToken);
        }
        /*
         * In all other cases (e.g. if the request contains a request parameter), use
         * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies
         * when a server-side rendered form includes the _csrf request parameter as a
         * hidden input.
         */
        return this.delegate.resolveCsrfTokenValue(request, csrfToken);
    }
}

final class CsrfCookieFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf");
        // Render the token value to a cookie by causing the deferred token to be loaded
        csrfToken.getToken();

        filterChain.doFilter(request, response);
    }
}

Security Config looks like this

@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
    http.sessionManagement(management -> management.maximumSessions(-1).sessionRegistry(sessionRegistry()).expiredUrl("/api/expire-me"))
            .csrf(csrf -> {
                CookieCsrfTokenRepository csrfRepo = CookieCsrfTokenRepository.withHttpOnlyFalse();
                SpaCsrfTokenRequestHandler requestHandler = new SpaCsrfTokenRequestHandler();
                csrf.requireCsrfProtectionMatcher(new CsrfSecurityRequestMatcher()).csrfTokenRepository(csrfRepo).csrfTokenRequestHandler(requestHandler);
            }).addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class)...
}

Edit: Using fetch worked because i can attach csrf token to the header

function downloadFile(url, method, data, transactionId) {
const headers = new Headers({
    'Content-Type': 'application/x-www-form-urlencoded',
    'X-XSRF-TOKEN': Cookies.get('XSRF-TOKEN')
});
const reqParams = new URLSearchParams();
reqParams.append('t', transactionId);
reqParams.append('values', JSON.stringify(data));
fetch(url, {
    method: 'POST', // *GET, POST, PUT, DELETE, etc.
    credentials: 'same-origin', // include, *same-origin, omit
    redirect: 'follow', // manual, *follow, error
    referrerPolicy: 'strict-origin-when-cross-origin', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
    body: reqParams, // body data type must match "Content-Type" header
    headers: headers
})
    .then((response) => response.blob())
    .then((blob) => {
        // Create  blob  URL
        const blobUrl = window.URL.createObjectURL(blob);

        // Create a temporary anchor el
        const anchorEl = document.createElement('a');
        anchorEl.href = blobUrl;
        anchorEl.download = `history.pdf`; // Set any filename and extension
        anchorEl.style.display = 'none';

        // Append the a tag to the DOM and click it to trigger download
        document.body.appendChild(anchorEl);
        anchorEl.click();

        // Clean up
        document.body.removeChild(anchorEl);
        window.URL.revokeObjectURL(blobUrl);
    })
    .catch((error) => {
        console.error('Error downloading file:', error);
    });
}
0

There are 0 best solutions below