Header in the response must not be the wildcard '*' when the request's credentials mode is 'include'
Asked Answered
D

6

51

I'm using Auth0 for my user authentication to only allow logged in users to access a Spring (Boot) RestController. At this point I'm creating a real-time message functionality where users can send messages from the Angular 2 client (localhost:4200) to the Spring server (localhost:8081) using stompjs and sockjs.

When trying to create a Stomp-client and starting a connection I receive the following console-error:

 The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:4200' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.

After researching this problem it looks like it is not possible to set the option origins = * and credentials = true at the same time. How can I resolve this when I've already set the allowed origin in the WebSocketConfig to the client domain?

Angular 2 component

connect() {
    var socket = new SockJS('http://localhost:8081/chat');
    this.stompClient = Stomp.over(socket);  
    this.stompClient.connect({}, function(result) {
        console.log('Connected: ' + result);
        this.stompClient.subscribe('/topic/messages', function(message) {
            console.log(message);
        });
    });
}    

WebSocketConfig

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat").setAllowedOrigins("http://localhost:4200").withSockJS();
    }
}

localhost:8081/chat/info?t=1490866768565

{"entropy":-1720701276,"origins":["*:*"],"cookie_needed":true,"websocket":true}

MessageController

public class MessageController {
    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public Message send(Message message) throws Exception {
        return new Message(message.getFrom(), message.getText());
    }
}

SecurityConfig (temporarily permits all)

public class SecurityConfig extends Auth0SecurityConfig {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests().anyRequest().permitAll();
    }
}

UPDATE

After some more testing and researching it seems that the problem only happens using Chrome. Problem maybe related to: https://github.com/sockjs/sockjs-node/issues/177

UPDATE

I created the CORSFilter like chsdk mentioned and used the addFilterBefore() method: https://mcmap.net/q/89147/-spring-boot-security-cors.

@Bean
CORSFilter corsFilter() {
    CORSFilter filter = new CORSFilter();
    return filter;
}

@Override
protected void configure(HttpSecurity http) throws Exception {
    http.addFilterBefore(corsFilter(), SessionManagementFilter.class).authorizeRequests().anyRequest().permitAll();
    http.csrf().disable();
}

I can see that the Filter is called by debugging it but the error message keeps appearing on the clientside even if the correct Access-Control-Allow-Origin gets set:

enter image description here

Dilorenzo answered 30/3, 2017 at 10:9 Comment(0)
G
38

Problem:

You are not configuring 'Access-Control-Allow-Origin' correctly and your current configuration is simply ignored by the server.

Situation:

The Error stack trace says:

The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'http://localhost:4200' is therefore not allowed access.

It means that apart from the fact that you can't set 'Access-Control-Allow-Origin' to the wildcard "*", your domain 'http://localhost:4200' is not allowed access too.

To answer your question:

How can I resolve this when I've already set the allowed origin in the WebSocketConfig to the client domain?

Solution:

I guess you don't need to set the allowed origin in the WebSocketConfig because it's meant to configure WebSocket-style messaging in web applications as stated in WebSocket Support in Spring documentation, you will need to configure it in a CORSFilter configuration class as it's meant to configure Spring Filters for Web application access.

This is what you will need in your CORSFilter.java configuration class:

public class CORSFilter implements Filter {

    // This is to be replaced with a list of domains allowed to access the server
  //You can include more than one origin here
    private final List<String> allowedOrigins = Arrays.asList("http://localhost:4200"); 

    public void destroy() {

    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
        // Lets make sure that we are working with HTTP (that is, against HttpServletRequest and HttpServletResponse objects)
        if (req instanceof HttpServletRequest && res instanceof HttpServletResponse) {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;

            // Access-Control-Allow-Origin
            String origin = request.getHeader("Origin");
            response.setHeader("Access-Control-Allow-Origin", allowedOrigins.contains(origin) ? origin : "");
            response.setHeader("Vary", "Origin");

            // Access-Control-Max-Age
            response.setHeader("Access-Control-Max-Age", "3600");

            // Access-Control-Allow-Credentials
            response.setHeader("Access-Control-Allow-Credentials", "true");

            // Access-Control-Allow-Methods
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");

            // Access-Control-Allow-Headers
            response.setHeader("Access-Control-Allow-Headers",
                "Origin, X-Requested-With, Content-Type, Accept, " + "X-CSRF-TOKEN");
        }

        chain.doFilter(req, res);
    }

    public void init(FilterConfig filterConfig) {
    }
}

You can see the use of :

private final List<String> allowedOrigins = Arrays.asList("http://localhost:4200");

To set the list of domains allowed to access the server.

References:

You may need to take a look at CORS support in Spring Framework and Enabling Cross Origin Requests for a RESTful Web Service for further reading about it.

Gameness answered 14/4, 2017 at 9:42 Comment(14)
Can you please check my edited question and tell me if I forgot something? Filter is getting called but the error message keeps showing up clientside.Dilorenzo
@Dilorenzo I am sorry if I am late with my response, I see that you are using http.csrf().disable() in your configuration, I think you don't need it in your case..Coriss
No problem. I used the http.csrf().disable() because a request from my client to the server resulted in a csrf error (can't remember details). I will try removing it as soon as I get home and will let you know if it worked.Dilorenzo
Removing the http.csrf().disable() did not help. But what I didn't noticed before is that because of the CORSFilter the error message has changed a bit: .. Origin 'localhost:4200' is therefore not allowed access .. So the origin is set correctly but the error keeps showing up.Dilorenzo
@Dilorenzo Can you please share the error stack trace again?Coriss
I only received the error message: XMLHttpRequest cannot load localhost:8081/message/info?t=1492619034754. The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. Origin 'localhost:4200' is therefore not allowed access. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute. There is no other stack trace as far as I'm aware of.Dilorenzo
@Dilorenzo I think the problem is in relation with SockJS because the Spring configuration is set correctly.Coriss
Do you think the problem is within my code or that it is just a general SockJS issue/bug?Dilorenzo
@Dilorenzo No your code seems to be correct, it's rather a bug with SockJS as discussed in the issue discussion you shared in your update.Coriss
Ok thanks chsdk, I'll take another look into this bug and if needed start looking for a SockJS alternative.Dilorenzo
You are welcome @Sam, I hope you can find a suitable solution.Coriss
@Dilorenzo did you find a solution? Thank you.Nel
@pcsantana, it has been a while but if I recal I think it was more of a SockJS bug so I went for a full StompJS solution. See my comment below my question: github.com/sockjs/sockjs-node/issues/227.Dilorenzo
OK, I will see that! ThanksNel
M
6

This has nothing to do with your spring or angular app code.

Intro to your problem
The Access-Control-Allow-Origin is a part of CORS (Cross-Origin Resource Sharing) mechanism that gives web servers cross-domain access controls. It is in place to protect your app/site from CSRF (Cross-Site Request Forgery).

CORS / CSRF

The problem
Now if we read your error carefully

The value of the 'Access-Control-Allow-Origin' header in the response must 
not be the wildcard '*' when the request's credentials mode is 'include'. 
Origin 'http://localhost:4200' is therefore not allowed access.

It says that Access-Control-Allow-Origin header cannot be a wildcard.

With other words, now your back-end is saying everybody from allover the web can run code on my site.

What we want to achieve: Limit the origin to only your front-end app (ng2).

Solution Now because you are using Spring I will assume that you are using it with Apache Tomcat as your back-end webserver.

CORS are difined as filter in your web.conf (tomcat folder)

find this line

<init-param>
  <param-name>cors.allowed.origins</param-name>
  <param-value>*</param-value>
</init-param>

and change the * to http://localhost:4200

for more information about config of CORS in Tomcat please read this

EDIT ( Spring boot )
Because you are using spring boot, you can delegate configuration of cors to the framework.

Please follow this tutorial on spring.io ( like chsdk proposed )to get a better grasp of CORS configuration with spring boot .

Methodist answered 14/4, 2017 at 8:32 Comment(2)
I'm using Tomcat / Spring Boot and cannot find the web.conf file. I assume that I need to configure Tomcat differently than your suggestion because of Spring Boot?Dilorenzo
@Dilorenzo the problem is related to your application configuration rather than Tomcat configuration, you should follow the configuration from Spring documentation (stated in my answer), by the way Tomcat can be configured in application.properties file in your Spring boot application.Coriss
T
4

my answer is too late but i'm posting this if anyone could face the same problem, i've been facing the same cross-origin issue.

Basically if you are using Spring Security implemented on your server side application, Probably it is he who blocks websocket handshaker

You have to tell Spring security to allow your websocket endpoints in order to allow socket handshake... using

.antMatchers("/socket/**").permitAll()

So sockjs will be able now to send a GET (Http) request for handshaking before switching to Websocket protocol

This is Spring security Configuration

package org.souhaib.caremy.security.module.config;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
            .exceptionHandling().authenticationEntryPoint(restAuthenticationEntryPoint).and()
            .authorizeRequests()
            .antMatchers(SecurityParams.PUBLIC_ROUTES).permitAll()
            .antMatchers("/socket/**").permitAll();

    http.csrf().disable();
}}

This is WebSocket Broker configuration

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/socket")
                .setAllowedOrigins("http://localhost:4200")
                .withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app")
                .enableSimpleBroker("/chat");
    }
}
Trifling answered 18/2, 2018 at 19:7 Comment(1)
Plus if you are running a custom CorsConfigurationSource don't forget to set the Orgin there tooWulfenite
S
2

you just need to configure your Cors like this below

and othorize OPTION method as given in my class

    @Component
public class CORSFilter extends GenericFilterBean {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        HttpServletResponse response = (HttpServletResponse) servletResponse;
        String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin );

             response.setHeader("Access-Control-Allow-Credentials", "true");

        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, Sec-Fetch-Mode, Sec-Fetch-Site, Sec-Fetch-Dest");
        response.addHeader("Access-Control-Expose-Headers", "xsrf-token");
        if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }
    }

pay attention to this code. because it will redirect the first OPTION request from browser

if ("OPTIONS".equals(request.getMethod())) {
            response.setStatus(HttpServletResponse.SC_OK);
        } else {
            filterChain.doFilter(request, response);
        }

at this line of configuration you allow credentials

 response.setHeader("Access-Control-Allow-Credentials", "true");

and from this line you accept every orgin it will take de browser orgin it's not recommanded but ...

String origin = request.getHeader("Origin");
        response.setHeader("Access-Control-Allow-Origin", origin );
Suffuse answered 8/4, 2022 at 12:49 Comment(0)
E
0

If you are using Spring Security 6:

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
    http
        .cors(Customizer.withDefaults())
        .authorizeHttpRequests(
            (authorize) -> authorize
                .anyRequest().authenticated()
        )
        .httpBasic(Customizer.withDefaults())
        .formLogin(Customizer.withDefaults());

    return http.build();
}

@Bean
CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:3000"));
    configuration.setAllowedMethods(Arrays.asList("GET", "POST"));
    configuration.setAllowCredentials(true);
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}

Reference

Erastus answered 19/3 at 9:22 Comment(0)
M
-3

Just add .setAllowedOrigins("*") at webSocket config.

@Override
public void registerStompEndpoints(StompEndpointRegistry stompEndpointRegistry) {
    stompEndpointRegistry.addEndpoint("/yourEndpoint");
    stompEndpointRegistry.addEndpoint("/yourEndpoint").setAllowedOrigins("*").withSockJS();
}

The version of webSocket is 1.4.1.RELEASE,you should update your version if the method wasn't shown.

Manizales answered 3/7, 2017 at 11:11 Comment(1)
This doesn't work for this case since the wildcard is already set but is having issues due to the fact 'Access-Control-Allow-Origin' wasn't specific for localhost:4200Allochthonous

© 2022 - 2024 — McMap. All rights reserved.