Thanks to being nudged towards implementing debug logs, I can now see that the issue is that Spring is reporting an invalid CSRF token found for the notices controller. So far I've checked the headers postman generates and compared them to the ones generated through the fetch requests, and, found no difference. The token that was generated is successfully placed into the header of the request. Unfortunately there is nothing discernible in the Spring logs, so the debugging continues.
I'm working on learning Spring Security and currently connecting the frontend React portion to the Spring backend. I'm having trouble because when the POST
request is made to the desired endpoint, it returns an error of 401. This is confusing to me because I believe I have correctly configured CORS and also marked the end points as permit all.
In short, the process calls an endpoint /token
to get a CSRF token, then calls /notices
and passes the token in as a header. If done with Postman, the process works as expected, so I had thought it was a CORS issue, however, I've tried running the frontend on a different port and it was blocked by CORS so I think the issue is somewhere else.
Some additional info:
/notices
and /token
are both POST
operations.The code for the frontend JavaScript call is:
const debugNotices = () => {
let tokenData:any;
fetch('http://localhost:8080/token', {method:"POST"})
.then((response) => response.json())
.then((data) => tokenData = data).then((data:any) => fetch("http://localhost:8080/notices",
{
method:"POST",
headers: {
"X-XSRF-TOKEN": tokenData.token
}
}))
}
Spring security config:
@Bean
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
http
.cors()
.configurationSource(new CorsConfigurationSource() {
@Override
public CorsConfiguration getCorsConfiguration(HttpServletRequest request) {
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOrigins(Collections.singletonList("http://localhost:3000"));
config.setAllowedMethods(Collections.singletonList(("*")));
config.setAllowCredentials(true);
config.setAllowedHeaders(Collections.singletonList("*"));
config.setMaxAge(3600L);
return config;
}
})
.and()
.csrf()
.ignoringRequestMatchers("/contact", "/register", "/token")
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.and()
.securityContext()
.requireExplicitSave(false)
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.and()
.authorizeHttpRequests()
.requestMatchers("/myAccount", "/myBalance", "/myLoans", "/myCards", "/user").authenticated()
.requestMatchers("/notices", "/contact", "/register", "/test", "/token").permitAll()
.and()
.httpBasic()
.and()
.formLogin();
return http.build();
}
I've tried including
credentials:'include'
in the request body, however it causes a login prompt that I don't believe is the direction I'm looking for.
I've also tried manually inserting the CSRF token instead of requesting the data from the server, with the same failed results.
I've also tested CORS as far as I know how to, accessing the endpoints from anything other than localhost:3000
gets denied with a CORS error as expected.
This issue only happens when the React frontend is accessed from localhost:portNumber, I was able to work around the issue completely by instead using my local IP address in place of localhost.
192.168.0.105:3000 for example.
I'm still unsure why there is an issue using localhost in the URL, and, would love to hear why this is happening if you know.