Getting a Post 403 Forbidden with Spring Boot (VueJS and Axios Frontend)
Solution 1
When you configure Axios, you can simply specify the header once and for all:
import axios from "axios";
const CSRF_TOKEN = document.cookie.match(new RegExp(`XSRF-TOKEN=([^;]+)`))[1];
const instance = axios.create({
headers: { "X-XSRF-TOKEN": CSRF_TOKEN }
});
export const AXIOS = instance;
Then (here I assume you use SpringBoot 2.0.0, while it should work also in SpringBoot 1.4.x onward) in your Spring Boot application you should add the following security configs.
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
// CSRF Token
.csrf()
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse());
// you can chain other configs here
}
}
In this way, Spring will return the token as a cookie in the response (I assume you do a GET
first) and you will read it in the AXIOS configuration file.
Solution 2
You should not disable CSRF as per Spring Security documentation except, few special cases. This code will put the CSRF header to VUE. I used vue-resource.
//This token is from Thymeleaf JS generation.
var csrftoken = [[${_csrf.token}]];
console.log('csrf - ' + csrftoken) ;
Vue.http.headers.common['X-CSRF-TOKEN'] = csrftoken;
Hope this helps.
sm0keman1
Updated on June 05, 2022Comments
-
sm0keman1 almost 2 years
I've been having an issue with CORS and I have tried everything I could find on Stack Overflow and basically anything that I found on Google and have had no luck.
So I have user authentication on my backend and I have a login page on my frontend. I hooked up the login page with Axios so I could make a post request and tried to login but I kept getting errors like "Preflight request" so I fixed that then I started getting the "Post 403 Forbidden" error.
It appeared like this:
POST http://localhost:8080/api/v1/login/ 403 (Forbidden)
Even trying to login using Postman doesn't work so something is clearly wrong. Will be posting class files below
On my backend, I have a class called WebSecurityConfig which deals with all the CORS stuff:
@Configuration @EnableWebSecurity @EnableGlobalMethodSecurity(prePostEnabled = true) public class WebSecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Bean public WebMvcConfigurer corsConfigurer() { return new WebMvcConfigurerAdapter() { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedMethods("GET", "POST", "HEAD", "PUT", "DELETE", "OPTIONS"); } }; } @Bean public CorsFilter corsFilter() { UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); CorsConfiguration config = new CorsConfiguration(); config.setAllowCredentials(true); config.addAllowedOrigin("*"); // TODO: lock down before deploying config.addAllowedHeader("*"); config.addExposedHeader(HttpHeaders.AUTHORIZATION); config.addAllowedMethod("*"); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } @Override protected void configure(HttpSecurity http) throws Exception { http.headers().frameOptions().disable(); http .cors() .and() .csrf().disable().authorizeRequests() .antMatchers("/").permitAll() .antMatchers("/h2/**").permitAll() .antMatchers(HttpMethod.POST, "/api/v1/login").permitAll() .anyRequest().authenticated() .and() // We filter the api/login requests .addFilterBefore(new JWTLoginFilter("/api/v1/login", authenticationManager()), UsernamePasswordAuthenticationFilter.class); // And filter other requests to check the presence of JWT in header //.addFilterBefore(new JWTAuthenticationFilter(), // UsernamePasswordAuthenticationFilter.class); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { // Create a default account auth.userDetailsService(userDetailsService); // auth.inMemoryAuthentication() // .withUser("admin") // .password("password") // .roles("ADMIN"); } }
On our frontend which is written in VueJS and using Axios to make the call
<script> import { mapActions } from 'vuex'; import { required, username, minLength } from 'vuelidate/lib/validators'; export default { data() { return { form: { username: '', password: '' }, e1: true, response: '' } }, validations: { form: { username: { required }, password: { required } } }, methods: { ...mapActions({ setToken: 'setToken', setUser: 'setUser' }), login() { this.response = ''; let req = { "username": this.form.username, "password": this.form.password }; this.$http.post('/api/v1/login/', req) .then(response => { if (response.status === 200) { this.setToken(response.data.token); this.setUser(response.data.user); this.$router.push('/dashboard'); } else { this.response = response.data.error.message; } }, error => { console.log(error); this.response = 'Unable to connect to server.'; }); } } } </script>
So when I debugged via Chrome's tools (Network), I noticed that the OPTIONS request goes through as shown below:
Here is a picture of the POST error:
Here is another class which handles the OPTIONS request (JWTLoginFilter as referenced in the WebSecurityConfig):
public class JWTLoginFilter extends AbstractAuthenticationProcessingFilter { public JWTLoginFilter(String url, AuthenticationManager authManager) { super(new AntPathRequestMatcher(url)); setAuthenticationManager(authManager); } @Override public Authentication attemptAuthentication( HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException { AccountCredentials creds = new ObjectMapper() .readValue(req.getInputStream(), AccountCredentials.class); if (CorsUtils.isPreFlightRequest(req)) { res.setStatus(HttpServletResponse.SC_OK); return null; } return getAuthenticationManager().authenticate( new UsernamePasswordAuthenticationToken( creds.getUsername(), creds.getPassword(), Collections.emptyList() ) ); } @Override protected void successfulAuthentication( HttpServletRequest req, HttpServletResponse res, FilterChain chain, Authentication auth) throws IOException, ServletException { TokenAuthenticationService .addAuthentication(res, auth.getName()); } }
-
Andre about 4 yearsI'm using .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) but the problem is this just works for GET operations.... Why it doesn't work for PUT, POST and DELETE?