Reputation: 387
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
Upvotes: 1
Views: 1135
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