0

I have a problem with CORS error in my Angluar 8 app.

I receive response from server with status 200 and valid JWT token which, I think, means server is properly configured, but still got error in console:

Access to XMLHttpRequest at 'http://localhost:8080/login' from origin 'http://localhost:4200' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

The Angular app is running locally on my machine on port 4200

The server is my own Spring Boot app with Security, running also locally on port 8080

I have implemented custom Security Filter to handle CORS:

@Component
public class CorsFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletResponse response = (HttpServletResponse) servletResponse;

        response.setHeader("Access-Control-Allow-Origin", "http://localhost:4200");
        response.setHeader("Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS");
        response.setHeader("Access-Control-Allow-Headers", "*");
        response.setHeader("Access-Control-Allow-Credentials", "true");
        response.setHeader("Access-Control-Max-Age", "180");

        filterChain.doFilter(servletRequest, servletResponse);
    }
}

And added in Security config:

@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    private final RestAuthenticationSuccessHandler authenticationSuccessHandler;
    private final RestAuthenticationFailureHandler authenticationFailureHandler;
    private final CustomUserDetailsService userDetailsService;
    private final String secret;
    private final TenantFilter tenantFilter;
    private final CorsFilter corsFilter;
    private final UserTenantRelationRepository userTenantRelationRepository;

    @Autowired
    public SecurityConfig(RestAuthenticationSuccessHandler authenticationSuccessHandler,
                          RestAuthenticationFailureHandler authenticationFailureHandler,
                          CustomUserDetailsService userDetailsService,
                          @Value("${jwt.secret}") String secret, TenantFilter tenantFilter, CorsFilter corsFilter, UserTenantRelationRepository userTenantRelationRepository) {
        this.authenticationSuccessHandler = authenticationSuccessHandler;
        this.authenticationFailureHandler = authenticationFailureHandler;
        this.userDetailsService = userDetailsService;
        this.secret = secret;
        this.tenantFilter = tenantFilter;
        this.corsFilter = corsFilter;
        this.userTenantRelationRepository = userTenantRelationRepository;
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(corsFilter, SessionManagementFilter.class)
                .addFilterBefore(tenantFilter, BasicAuthenticationFilter.class)
                .csrf().disable()
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/login").permitAll()
                .antMatchers("/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilter(authenticationFilter(userTenantRelationRepository))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), userDetailsService, secret))
                .exceptionHandling()
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }

As I mentioned before, I receive response 200 and valid JWT token, so I am very confused about it.

EDIT

Ok, so I had read about preflight request that you mentioned and fixed my filter to handle this request.

Now it looks like this:

@Component
public class CorsFilter extends OncePerRequestFilter {

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
        response.setHeader("Access-Control-Max-Age", "3600");
        response.setHeader("Access-Control-Allow-Headers", "authorization, content-type, xsrf-token");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }
    }
}

And configuration is now:

@Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .addFilterBefore(corsFilter, ChannelProcessingFilter.class)
                .addFilterBefore(tenantFilter, BasicAuthenticationFilter.class)
                .csrf().disable()
                .anonymous().disable()
                .authorizeRequests()
                .antMatchers("/").permitAll()
                .antMatchers("/login").permitAll()
                .antMatchers("/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .addFilter(authenticationFilter(userTenantRelationRepository))
                .addFilter(new JwtAuthorizationFilter(authenticationManager(), userDetailsService, secret))
                .exceptionHandling()
                .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED));
    }

Thank you all for your tips.

Jakub Pawlak
  • 49
  • 1
  • 6

2 Answers2

1

if your cors request is other then GET the browser will issue an option request (preflight request) and it will not send authentication headers or anything like that. So in your security configuration you need to allow anonymous option request for that url.

Eldar
  • 9,781
  • 2
  • 10
  • 35
  • It would be more helpful if you provided example code for the OP to do this in spring boot. – Damian C Nov 16 '19 at 15:30
  • I have 0 experience with java or ant or spring. Check [this](https://stackoverflow.com/questions/40286549/spring-boot-security-cors) – Eldar Nov 16 '19 at 15:43
1

I would suggest you to do the Cors configuration in your backend using the FilterRegistrationBean. Usage would be as follows,

@Bean

public FilterRegistrationBean<CorsFilter> corsFilter() {

    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();

    CorsConfiguration config = new CorsConfiguration();

    config.setAllowCredentials(true);

    config.addAllowedOrigin("http://localhost:4200");

    config.addAllowedHeader("*");

    config.addAllowedMethod("*");

    source.registerCorsConfiguration("/**", config);

    FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<CorsFilter>(

            new CorsFilter(source));

    bean.setOrder(0);

    return bean;

}

Import using,

import org.springframework.boot.web.servlet.FilterRegistrationBean;

Do this inside your WebConfig.java file that has the @Configuration annotation.

Damian C
  • 2,111
  • 2
  • 15
  • 17
Angela Amarapala
  • 1,002
  • 10
  • 26