3

I'm trying to deploy a React + Spring Boot App.

3 docker containers with compose (client+server+database) and everything works on localhost. I can access the frontend, login/register, and use the web service as expected.

client: localhost:3333
server: localhost:8080

Once i try deploy it on another machine (desktop w/ ubuntu server), im able to acess the frontend / backend and send requests via postman (ex: POST a new user to 192.168.1.x:8080/api/account/register.)

192.168.1.x:3333
192.168.1.x:8080

Problem:

If i try to acess 192.168.1.x:3333 or 192.168.1.x:8080 in another machine (same network) or my-ip:333 (another network) it works, but pressing Login/Register wont work. (this only works on the host machine)

Devtools Firefox:

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8080/api/account/login. (Reason: CORS request did not succeed). Status code: (null).

Devtools Chrome:

Failed to load resource: net::ERR_CONNECTION_REFUSED localhost:8080/api/account/login:1          
TypeError: Failed to fetch

EDIT (23/05/2022)

I still couldn´t solve the issue

WebSecurity.java

@EnableWebSecurity
public class WebSecurity extends WebSecurityConfigurerAdapter {
    private UserDetailsService userDetailsService;
    private BCryptPasswordEncoder bCryptPasswordEncoder;

    @Autowired
    private DataSource dataSource;

    public WebSecurity(UserDetailsService userDetailsService, BCryptPasswordEncoder bCryptPasswordEncoder) {
        this.userDetailsService = userDetailsService;
        this.bCryptPasswordEncoder = bCryptPasswordEncoder;
    }


    @Override
    protected void configure(HttpSecurity http) throws Exception {

        // Comment to disable authentication
        http.cors().and().csrf().disable().authorizeRequests().antMatchers(HttpMethod.POST, ACESSABLE).permitAll()
                .anyRequest().authenticated().and().addFilter(new JWTAuthenticationFilter(authenticationManager()))
                .addFilter(new JWTAuthorizationFilter(authenticationManager()))
                // this disables session creation on Spring Security
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        // auth.userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder);
        auth.jdbcAuthentication().dataSource(dataSource)
                .usersByUsernameQuery("select username, password, 1 from users where username=?")
                .authoritiesByUsernameQuery(
                        "select u.username, r.name from users_roles ur, users u, role r where u.username = ? and ur.users_id = u.id and ur.role_id = r.id")
                .passwordEncoder(bCryptPasswordEncoder);
    }

    @Bean
    CorsConfigurationSource corsConfigurationSource() {
        System.out.println("setting cors configuration");
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();
        configuration.applyPermitDefaultValues();
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }

}

WebConfig.java

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void configurePathMatch(PathMatchConfigurer configurer) {
        AntPathMatcher matcher = new AntPathMatcher();
        matcher.setCaseSensitive(false);
        configurer.setPathMatcher(matcher);
    }
}

WHAT I'VE TRIED

tried as @Knox suggested, but still couldn´t make it work.

  1. not specifying allowedOrigins breaks the connection between frontend and backend on HOST machine (CORS ‘Access-Control-Allow-Origin’ missing), but from the outside it just shows the same as allways (CORS request did not succeed).
@Bean
    CorsConfigurationSource corsConfigurationSource() {
        System.out.println("setting cors configuration");
        final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        CorsConfiguration configuration = new CorsConfiguration();
        //configuration.applyPermitDefaultValues();
        //configuration.allowedOrigins("*");
        source.registerCorsConfiguration("/**", configuration);

        return source;
    }

enter image description here

  1. Swapping my WebConfig.java with the one provided by @Knox, everything works on HOST, still (CORS request did not succeed) from the outside

enter image description here

EDIT 2 (24/05/2022)

Just noticed my frontend container (React) is not responding to changes... my guess is the URL i've changed many times was never getting updated on docker compose restart. So changing URL from localhost:8080 to myip:8080 might fix it if i can get the container to reload.

fetch(URL + '/api/account/login', {

Dockerfile client

FROM node:8

WORKDIR /usr/scr/app
COPY package*.json ./

RUN npm install
COPY . .

CMD ["npm", "start"]

Docker-compose.yml

  client:
    build: ./client
    ports:
      - 3333:3000
    volumes:
      - ./frontend:/usr/src/app
      - /usr/src/app/node_modules
    networks:
      - network_backend



zemmsoares
  • 313
  • 1
  • 8
  • 27
  • Does [this](https://stackoverflow.com/questions/46337471/how-to-allow-cors-in-react-js) answer your question? – H3AR7B3A7 May 23 '22 at 22:43
  • Im guessing you might be using a proxy to connect to the java backend in the react, check your package.json. If yes, then that proxy is not working when deploying and you need to take that into account – innocent May 24 '22 at 03:08
  • Passing --build to docker compose and updating URL to my IP fixed the issue. now everything is working as expected. – zemmsoares May 24 '22 at 09:46

3 Answers3

2

Here is your error

Failed to load resource: net::ERR_CONNECTION_REFUSED localhost:8080/api/account/login:1 TypeError: Failed to fetch

In react you have configured the URL with which the frontend will call the backend server. In that configuration you have wrongly configured the url as localhost:8080.

The React Frontend normally runs in the clients browser (If you don't have it configured otherwise) which I suppose is the case also here.

So when the browser of the client tries to call localhost:8080 it is able to do so when the client sits on the same machine where the backend server runs, as the same machine understands that the backend server runs on localhost:8080.

But when the client sits to some other machine inside the network, the Frontend (clients browser) tries again to call localhost:8080 but that machine does not have locally the server running. Therefore the error.

Solution

Configure the React application code, so that it uses as address to reach the backend not localhost:8080 but some address that is known from all hosts in the network, meaning the ip address of the machine where the server runs. Could be 192.168.1.x:8080 as you say that this is reachable from some user outside of the machine where the server runs.

Panagiotis Bougioukos
  • 15,955
  • 2
  • 30
  • 47
1

The requests are failing because you are missing a CORS configuration.

Your browser typically blocks requests that originate from one origin (in your case, http://192.168.1.x:3333) to another (http://192.168.1.x:8080) due to Same-origin policy.

When you want to allow requests to your backend from a different origin than the one it is hosted on, you'll need to configure your backend to respond with the appropriate CORS headers that tell the browser that access from a specific, different origin is allowed.

Your requests might be succeeding when running with localhost as browsers will be more lenient with localhost, rather than something that could be hosted publicly.

The approaches to configuring CORS in Spring Boot are described here, with one of the methods being annotation-based:

@CrossOrigin("http://192.168.1.x:3333")
@RestController
public class MyCrossOriginController {

    // this should now allow requests with an origin of http://192.168.1.x:3333)
    @PostMapping("/api/account/register")
    public Object register(...) {
        // ...
    }

See the Javadocs for the more advanced capabilities of the @CrossOrigin annotation.

Or, you can register it through the WebMvcConfigurer to have it apply globally or by path:

@Configuration
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/api/**")
            .allowedOrigins(
                    "http://192.168.1.x:3333",
                    "http://localhost:3333");
    }
}

The above allows frontends hosted at http://192.168.1.x:3333 and http://localhost:3333 to use /api/... endpoints.

Knox
  • 1,150
  • 1
  • 14
  • 29
  • Thanks @Knox i've updated the question, no matter what i've change for outside connections allways returns ```(Reason: CORS request did not succeed)```. What makes me think is the POST request that fails by accesing it from the outside, it tries to POST to ```http://localhost:8080/api/account/login``` instead ```http://myip:8080/api/account/login``` – zemmsoares May 23 '22 at 20:10
  • @zemmsoares I'm not quite understanding what is and what isn't working after your comment/edit. If your frontend is POSTing to the wrong place (`localhost:8080` instead of `myip:8080`), then you need to update that URL in your frontend. Every origin (frontend) you plan to access the API from should be configured as an allowed origin. I've updated the latter example (`WebConfig`) to also allow `http://localhost:3333` as an origin. Was that the issue? – Knox May 23 '22 at 23:56
  • Just realized my React container isn't updating any changes, the problem is most likely to be the URL ```fetch(URL + '/api/account/login', {```. now i need to figure out why my backend container is updating on docker compose restart and frontend not. – zemmsoares May 24 '22 at 01:57
0

I think that is important to be sure what methods is allowed to make the CORS request.

@Bean

CorsConfigurationSource corsConfigurationSource() {
    CorsConfiguration configuration = new CorsConfiguration();
    configuration.setAllowedOrigins(Arrays.asList("http://localhost:3333","http://192.168.1.x:3333"));
    configuration.setAllowedMethods(Arrays.asList("GET","POST","PATCH","DELETE"));
            configuration.setAllowCredentials(Boolean.TRUE);
            //configuration.setAllowedHeaders(Arrays.asList("Authorization", "Cache-Control", "Content-Type"));
    UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
    source.registerCorsConfiguration("/**", configuration);
    return source;
}