Home > front end >  Getting Axios from React to Work with Spring Boot Spring Boot Security
Getting Axios from React to Work with Spring Boot Spring Boot Security

Time:06-29

I have created a react app with a spring boot backend but I'm having trouble pulling the data through with axios.

I have checked numerous SO posts as well as documentation with spring to no avail. I was initially blocked by CORS but I was able to resolve that with Spring Security. However, spring security requires authentication, I've been using the default user "user" with the randomly generated password (since I can't get a newly defined user/password defined with AuthenticationManagerBuilder to work just with queries against the server directly in a browser but that's an issue for another day). Below is my configuration file for the server.

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        
        http
            .authorizeRequests()
            .anyRequest().authenticated()
            .and()            
            .httpBasic();
        http.cors().and();
    }    

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

}

My server runs on localhost port 9898, the query I'm initially trying to pull data from on the front end is a get by id for contact info which goes against http://localhost:9898/api/Contact/1 when I successfully call the server from a browser directly the header details are as depicted: call from browser to server general and response headers call from browser to server request headers

notice that authorization header is actually there in the request.

For the RestController I've got cross origin set to the client running on port 3000. I do have a header being adding in the getContactMethod as suggested in a tutorial but I don't think this actually changed anything since I have this header being set in the configuration file anyway.

@CrossOrigin(origins = "http:localhost:3000")
@RestController
@RequestMapping("/api/Contact")
public class ContactController {
    
    @Autowired
    private ContactRepository ContactRepository;

    @GetMapping("/")
    public List<Contact> getContacts(){
        return this.ContactRepository.findAll();
    }

    @GetMapping("/{id}")
    public Contact GetContact(@PathVariable Long id, HttpServletResponse response){
        response.setHeader("Access-Control-Allow-Origin", "**");
        return ContactRepository.findById(id).orElse(null);

    }

For the client I have a file creating an axios instance, I'm not sure if this part is right since I never reference the specific instance again but maybe axios can figure this out on it's own if there is only one instance.

import axios from "axios";

const api = axios.create({
    baseURL: "http://localhost:9898",
});

// api.defaults.headers.common = {
//     'X-Requested-With': 'XMLHttpRequest'
// };

export default axios;

Now for the actual page on the front end I am attempted to load the requested data into the state variable from the useEffects event, which will need to be modified a little bit further but I can't go forward with that until the request works.

I've got numerous headers loaded in based on a combination of what I've come across online but the one I want to focus on is the authentication since that won't actually show up in the request when I look with dev tools on the network. I've got the password which is randomly set by spring security each time the back end is run hard coded and then this hard coded user:password value is encoded and added to the headers. Below is the relevant code:

const tok = 'user:9de3a921-a4af-4d51-b8d7-cf37b208916e';
  const hash = btoa(tok);
  const Basic = 'Basic '   hash;

  const headers = {
    "Cache-Control": "no-cache",
    "Accept-Language": "en",
    "Content-Type": "application/json",
    "Access-Control-Allow-Origin": "http://localhost:3000",
    "Access-Control-Allow-Methods": "DELETE, POST, GET, OPTIONS",
    "Access-Control-Allow-Headers": "Content-Type, Authorization, X-Requested-With",
    //"Authorization": "Basic dXNlcjowM2VhN2JhYS1mMTQ0LTQ5YWMtOGFhMy02NDE4YWJiNzdhMTk=",
    'Authorization': `Basic ${hash}`,
  };

  useEffect(() =>{
    console.log(Basic);
    axios.get("http://localhost:9898/api/Contact/1", headers)
    .then((res) => {
      console.log("data "   res.data);
      console.log("response header "   res.headers['Authorization']);
      setInfo(res.data);
    }).catch(err => console.log("error found "   err));
    console.log(info);
  }, []||[]);

When this is run I get a 401 unauthorized but for some reason the authorization header doesn't show up in the request headers.

General and response headers for request from client to server

Request headers for request from client to server

I feel like I'm fairly close with this but most of the tutorials on the spring site are simpler and the best practices for spring security have changed over the years so there is a lot of conflicting information and incomplete examples on the web. I figure I either have an issue in the security configuration or I guess I've set the headers up incorrectly but I don't have enough experience with spring and react I've just been troubleshooting in circles for a couple days.

Sources tried already (had to put some spaces in the links since I just made this account to post a question):

https://stackoverflow com/questions/36968963/how-to-configure-cors-in-a-spring-boot-spring-security-application/37610988#37610988

I should mention with this one below I added in .antMatchers(HttpMethod.Options, "/**").permitAll() and the headers were different but the request still didn't work and eventually the server would just crash shortly after starting with it

https://stackoverflow com/questions/41075850/how-to-configure-cors-and-basic-authorization-in-spring-boot/60933851#60933851

https://stackoverflow com/questions/58901586/how-to-fix-spring-security-authorization-header-not-being-passed

https://spring io/blog/2022/02/21/spring-security-without-the-websecurityconfigureradapter

https://spring io/guides/gs/rest-service-cors/

https://spring io/guides/gs/rest-service/

https://docs.spring io/spring-security/reference/reactive/integrations/cors.html

https://www.baeldung com/spring-security-cors-preflight

CodePudding user response:

There is an issue with how the headers are are being passed to axios. The axios documentation defines axios.get like this axios.get(url[, config])

There are two parameters here. The first is the url, and it is required. The second is an optional config object.

The config object has a headers field.

You should pass in the headers like this:

const headers = {
    'Cache-Control': 'no-cache',
    'Accept-Language': 'en',
    'Content-Type': 'application/json',
    'Access-Control-Allow-Origin': 'http://localhost:3000',
    'Access-Control-Allow-Methods': 'DELETE, POST, GET, OPTIONS',
    'Access-Control-Allow-Headers': 'Content-Type, Authorization, X-Requested-With',
    //"Authorization": "Basic dXNlcjowM2VhN2JhYS1mMTQ0LTQ5YWMtOGFhMy02NDE4YWJiNzdhMTk=",
    Authorization: `Basic ${hash}`
};

const config = {
    headers
};

axios.get('http://localhost:9898/api/Contact/1', config);

CodePudding user response:

I was looking back at the similar post I referenced earlier How to configure CORS in a Spring Boot Spring Security application? and I tried out the 4th most updated answer by user Soroosh Khodami Mar 11, 2021 and used their SecurityConfig.

@Override
    protected void configure(HttpSecurity http) throws Exception {
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(List.of("Authorization", "Cache-Control", "Content-Type"));
        corsConfiguration.setAllowedOrigins(List.of("http://localhost:3000"));
        corsConfiguration.setAllowedMethods(List.of("GET", "POST", "PUT", "DELETE", "PUT","OPTIONS","PATCH", "DELETE"));
        corsConfiguration.setAllowCredentials(true);
        corsConfiguration.setExposedHeaders(List.of("Authorization"));
        
        // You can customize the following part based on your project, it's only a sample
        http.authorizeRequests().antMatchers("/**").permitAll().anyRequest()
                .authenticated().and().csrf().disable().cors().configurationSource(request -> corsConfiguration);

    }

My previous config was missing:

  1. setAllowedHeaders(Listof("Authorization",..))
  2. setAllowCredentials(true)
  3. setExposedHeaders(Authorization)

this then lead me to a secondary issed which was caused by setting the header in my restcontroller get by id method.

response.setHeader("Access-Control-Allow-Origin", "**");

I got an error saying this was not allowed so then I changed it to "/**" and it still complained so I just commented it out and it worked. So if a tutorial suggests this (for me it was a youtube video about getting cors to work) I believe it is out of date/not best practice when you should set Access-Control-Allow-Origin in your Web Security Configuration.

  • Related