iTchTheRightSpot
iTchTheRightSpot

Reputation: 387

XSRF-TOKEN cookie implemented with Spring boot is not saved in browser when deployed to server

I have a Spring Boot API application deployed on AWS EC2, utilizing session authentication and authorization. The application is accessed through Nginx, which acts as a proxy and routes requests to the application running on Docker with CSRF protection enabled. The CSRF protection is in place to safeguard POST routes, including login routes, despite being set to permit all in the security filter chain.

Additionally, I have a separate EC2 server hosting an Angular-based UI. My implementation right now is on load of login page, a request is made to the Spring app to obtain a CSRF token through a CSRF controller as per docs (look below). During local development, this process works fine. However, when deployed to the EC2 instance, login requests result in a 401 unauthorized response. Upon investigation, I discovered that the X-XSRF-TOKEN is not being saved to the browser.

An initial solution was disable CSRF protection to verify if session cookie would be saved to browser after a user signs in. The cookie(s) are saved.

Would anyone know why X-XSRF-TOKEN(s) aren't not saved when deployed. I also attached a screenshot of what token saved in local development.

Note both ec2 instances have SSL certificates.

angular interceptor

@Injectable()
export class CsrfInterceptor implements HttpInterceptor {

  tokenExtractor: HttpXsrfTokenExtractor = inject(HttpXsrfTokenExtractor);

  intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    const headerName = 'X-XSRF-TOKEN';
    const token = this.tokenExtractor.getToken() as string;

    if (token !== null && !req.headers.has(headerName)) {
      req = req.clone({ headers: req.headers.set(headerName, token) });
    }

    return next.handle(req);
  }

}

spring boot cors config

CorsConfiguration configuration = new CorsConfiguration();
        configuration.setAllowedOrigins(allowOrigins);
        configuration.setAllowedMethods(List.of("GET", "PUT", "POST", "DELETE", "OPTIONS"));
        configuration.setAllowedHeaders(List.of(CONTENT_TYPE, ACCEPT, "X-XSRF-TOKEN"));
        configuration.setExposedHeaders(List.of(LOCATION, "X-XSRF-TOKEN"));
        configuration.setAllowCredentials(true);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", configuration);

cookie filter as per docs

@Slf4j
    private static final class CsrfCookieFilter extends OncePerRequestFilter {
        @Override
        protected void doFilterInternal(
                HttpServletRequest request,
                HttpServletResponse response,
                FilterChain filterChain
        ) throws ServletException, IOException {
            CsrfToken csrfToken = (CsrfToken) request.getAttribute(CsrfToken.class.getName());
            response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken());
            filterChain.doFilter(request, response);
        }
    }

CSRF controller

@GetMapping(path = "/csrf")
    public CsrfToken csrf(CsrfToken csrfToken) {
        return csrfToken;
    }

csrf in security filter chain

.csrf(csrf -> csrf
                        .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )

nginx sites-available for ec2 instance where my spring app is deployed

server {
        listen 80 default_server;
        listen [::]:80 default_server;

        root /var/www/html;
        index index.html index.htm index.nginx-debian.html;

        server_name server.example.com;

 location / {
                proxy_pass http://localhost:8080;
                proxy_redirect off;
                proxy_set_header   Host             $host;
                proxy_set_header   X-Real-IP        $remote_addr;
        }
 }

Cookie bean based on docs

@Bean
    public CookieSerializer cookieSerializer() {
        var domain = this.environment.getProperty("${server.servlet.session.cookie.domain}");
        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setCookieName("JSESSIONID");
        cookieSerializer.setUseHttpOnlyCookie(true);
        cookieSerializer.setUseSecureCookie(true);
        cookieSerializer.setCookiePath("/");
        cookieSerializer.setSameSite("lax");
        cookieSerializer.setCookieMaxAge(3600);
        cookieSerializer.setDomainName(domain);

        var property = Optional.ofNullable(this.environment.getProperty("spring.profiles.active"));
        if (property.isPresent() && (property.get().equals("dev") || property.get().equals("test"))) {
            cookieSerializer.setUseHttpOnlyCookie(false);
            cookieSerializer.setUseSecureCookie(false);
            cookieSerializer.setCookieMaxAge(1800);
        }
        return cookieSerializer;
    }

Screen shot of X-XSRF-TOKEN in local development enter image description here

Upvotes: 1

Views: 1135

Answers (1)

iTchTheRightSpot
iTchTheRightSpot

Reputation: 387

For anyone who comes across this issue the solution as per docs. Consumer<ResponseCookie.ResponseCookieBuilder> has to be defined. My solution is like below.

CSRF config in security filterchain

var csrfTokenRepository = getCookieCsrfTokenRepository(domain, profile);
.csrf(csrf -> csrf
                        .csrfTokenRepository(csrfTokenRepository)
                        .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
                )

helper method

private static CookieCsrfTokenRepository getCookieCsrfTokenRepository(String domain, String profile) {
        boolean secure = profile.equals("prod") || profile.equals("stage");
        CookieCsrfTokenRepository csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse();
        Consumer<ResponseCookie.ResponseCookieBuilder> csrfCookieCustomizer = (cookie) -> cookie
                .domain(domain)
                .httpOnly(false)
                .secure(secure)
                .path("/")
                .sameSite("lax")
                .maxAge(-1);
        csrfTokenRepository.setCookieCustomizer(csrfCookieCustomizer);
        return csrfTokenRepository;
    }

Upvotes: 1

Related Questions