1

Hi I have this request in Postman.

enter image description here

How do I make the same request in Reactjs axios?

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { headers: { Authorization: 'Basic ' + window.btoa('my-trusted-client : secret') } } )            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

I tried the one above but it doesnt work. Thanks

============== Update: ==============

I have followed your suggestions and removed the spaces. The conversion without the spaces seems accurate when it comes to window.btoa but I still cannot make it work.

With this code:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                { 
                    Authorization: 'Basic ' + window.btoa('my-trusted-client:secret'),
                    "Content-Type": "application/json"
                } 
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

I even hardcoded the Authorization header as:

Authorization: "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0",

even tried:

"Authorization": "Basic bXktdHJ1c3RlZC1jbGllbnQ6c2VjcmV0",

Nothing works, Authorization header won't appear in the request.

I get this in my Chrome Network tab:

enter image description here

I found that I can add auth config in the axios so I tried this one:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                {                     
                    "Content-Type": "application/json"
                }, 

                auth: {
                    username: 'my-trusted-client',
                    password: 'secret'
                }
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

Still, no Authorization header... Please help. Thanks.

============== Update2 ==============

I'm gonna add some information.

my Springboot configuration goes like this:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**").allowCredentials(true)               
                .allowedHeaders("Authorization", "Cache-Control", "Content-Type", "Accept", "X-Requested-With", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
                .exposedHeaders("Access-Control-Expose-Headers", "Authorization", "Cache-Control", "Content-Type", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Origin")
                .allowedMethods("GET", "OPTIONS", "POST", "PUT", "DELETE", "PATCH");
    }
}

the last reactJS request I used is this:

executeAuthenticationService(username, password) {
        return axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
            { 
                headers: 
                {                     
                    "Content-Type": "application/json"
                }, 

                auth: {
                    username: 'my-trusted-client',
                    password: 'secret'
                }
            })            
            .catch(
                (error) => {
                    console.log('error: ' + error);
                }
            );
    }

When I click my submit button from my login-form

I get this from Chrome tab (the whole thing):

enter image description here

And then I get this on my Java Console:

2019-07-11 06:40:47.008 DEBUG 8068 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : "ERROR" dispatch for OPTIONS "/error?grant_type=password&username=bill&password=abc123", parameters={masked}
2019-07-11 06:40:47.009 DEBUG 8068 --- [io-8080-exec-10] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped to public void org.springframework.web.servlet.handler.AbstractHandlerMethodMapping$EmptyHandler.handle()
2019-07-11 06:40:47.010 DEBUG 8068 --- [io-8080-exec-10] o.s.web.servlet.DispatcherServlet        : Exiting from "ERROR" dispatch, status 401

I have no idea what else did I miss. Please help. Thank you very much.

Alvin
  • 136
  • 2
  • 5
  • 12

5 Answers5

0

Try this (remove spaces):

    headers: { 
        Authorization: 'Basic ' + window.btoa('my-trusted-client:secret') 
    }

According to here

blonial
  • 81
  • 5
0

You can try this:

axios.get(`${API_URL}/oauth/token?grant_type=password&username=${username}&password=${password}`,
        {
          headers: {
            Authorization: "Basic " + window.btoa("my-trusted-client : secret"),
            "Content-Type": "application/json",
          },
        }
      )
      .then(resp => {})
      .catch(error => {
        console.log("error: " + error);
      });
mitesh7172
  • 666
  • 1
  • 11
  • 21
0
axios.defaults.headers.common['Authorization'] = AUTH_TOKEN;

you can try this to set header for all your request

Anh Tuan
  • 1,113
  • 5
  • 12
0

Could it be that you are missing the method for OPTIONS on your Access-Control-Allow-Methods?

Which means you could be declined before you even generate your whole request?

Or possibly disable your OPTIONS request as being part of authorisation similar to what's done here: https://www.baeldung.com/spring-security-cors-preflight

  • Hi, this is exactly what I am thinking literally like 10minutes ago discussing this with my friend in the kitchen. I will try it out when I get home tonight. Thanks. – Alvin Jul 10 '19 at 21:55
0

I lost my desire for living trying to deal with problems like these when playing basic auth with Spring and Axios.

After trying almost everything, even configuring like yours, I found a solution for my use case that can be yours too.

Axios request

I use Axios like this to make requests, notice that there is an auth param:

  async removeAnexo(uuidAnexo, successCallback, errorCallback) {
    const credentials = JSON.parse(Cookies.get("credentials"));

    const axiosRequest = axios.create({
      baseURL: serverUrl,
      auth: {
        username: credentials.username,
        password: credentials.password
      },
      headers: {
        "Content-Type": "application/json"
      }
    });
    await axiosRequest
      .post("processos/anexo/" + uuidAnexo + "/remove")
      .then(res => {
        if (res.status === 200) {
          successCallback();
          return res;
        }
      })
      .catch(error => {
        errorCallback(error);
      });
  }

Spring Configuration

  1. You need to have an Authentication Entry Point to expose a few necessary headers when preflight fires an OPTIONS request to the endpoint before making the actual request you asked for.
    import java.io.IOException;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.http.HttpHeaders;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
    import org.springframework.stereotype.Component;

    @Component
    public class AuthEntryPoint extends BasicAuthenticationEntryPoint {

        @Override
        public void commence(HttpServletRequest request, HttpServletResponse response,
                org.springframework.security.core.AuthenticationException authException)
                throws IOException, ServletException {
            if (HttpMethod.OPTIONS.matches( request.getMethod() )) {
                response.setStatus( HttpServletResponse.SC_OK );
                response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, request.getHeader( HttpHeaders.ORIGIN ) );
                response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_HEADERS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_HEADERS ) );
                response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_METHODS, request.getHeader( HttpHeaders.ACCESS_CONTROL_REQUEST_METHOD ) );
                response.setHeader( HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, "*" );
                response.setHeader( HttpHeaders.ACCESS_CONTROL_ALLOW_CREDENTIALS, "true" );
            } else {
                response.sendError( HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage() );
            }
        }

        @Override
        public void afterPropertiesSet() throws Exception {
            this.setRealmName( "simpleRealm" );
            super.afterPropertiesSet();
        }

    }

  1. Then you need to provide a custom filter, a cors one, also exposing a few headers, necessary for the SessionManagementFilter.
    import java.io.IOException;

    import javax.servlet.Filter;
    import javax.servlet.FilterChain;
    import javax.servlet.FilterConfig;
    import javax.servlet.ServletException;
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.stereotype.Component;

    @Component
    public class CorsFilter implements Filter {

        @Override
        public void init(FilterConfig filterConfig) throws ServletException {

        }

        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
                throws IOException, ServletException {
            HttpServletResponse response = (HttpServletResponse) servletResponse;
            HttpServletRequest request = (HttpServletRequest) servletRequest;

            response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
            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-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
            response.setHeader( "Access-Control-Max-Age", "180" );
            filterChain.doFilter( servletRequest, servletResponse );
        }

        @Override
        public void destroy() {

        }
    }

  1. Now, you have to provide an AuthenticationProvider, in my case I configured a CustomAuthenticationProvider based in BasicAuth.
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.core.env.Environment;
    import org.springframework.core.env.Profiles;
    import org.springframework.security.authentication.AuthenticationProvider;
    import org.springframework.security.authentication.BadCredentialsException;
    import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.core.authority.SimpleGrantedAuthority;
    import org.springframework.security.core.userdetails.UsernameNotFoundException;
    import org.springframework.security.crypto.password.PasswordEncoder;
    import org.springframework.stereotype.Component;

    import com.pontosistemas.clif.entities.system.Login;
    import com.pontosistemas.clif.repositories.system.LoginRepository;

    @Component
    public class CustomAuthenticationProvider implements AuthenticationProvider {

        @Autowired
        private LoginRepository loginRepository;

        @Autowired
        private Environment env;

        @Autowired
        private PasswordEncoder passwordEncoder;

        @Override
        public Authentication authenticate(Authentication auth) throws AuthenticationException {
            boolean notProductionProfile = env.acceptsProfiles( Profiles.of( "default", "homolog" ) );

            String username = auth.getName();
            String password = auth.getCredentials().toString();

            if (notProductionProfile && "admin".equals( username ) && "admin".equals( password )) {
                return new UsernamePasswordAuthenticationToken( username, password, Arrays.asList( new SimpleGrantedAuthority( "ADMIN" ), new SimpleGrantedAuthority( "protected" ) ) );
            } else {
                boolean usernameActiveExists = loginRepository.existsLoginByUsernameAndActiveIsTrue( username );

                if (usernameActiveExists) {

                    Login login = loginRepository.findByUsernameAndActiveIsTrue( username );

                    String hashPassword = login.getPassword();

                    boolean matches = passwordEncoder.matches( password, hashPassword );

                    if (matches)
                        return new UsernamePasswordAuthenticationToken( username, hashPassword, Arrays.asList( new SimpleGrantedAuthority( login.getRole().toString() ), new SimpleGrantedAuthority( "protected" ) ) );
                    else
                        throw new BadCredentialsException( "Senha inválida!" );
                }
            }
            throw new UsernameNotFoundException( "Nome de usuário não encontrado" );

        }

        @Override
        public boolean supports(Class<?> auth) {
            return auth.equals( UsernamePasswordAuthenticationToken.class );
        }
    }
  1. Finally, you gonna need a glue to put all these parts together, this glue is a WebSecurityConfigurerAdapter, you can look carefully for more details, try to comment each configuration line to see what happens. In my case I configured what happens when login with success, or logout, or login failure. For each case there is a handler to implement.
    import java.io.IOException;
    import java.util.HashMap;
    import java.util.Map;

    import javax.servlet.ServletException;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;

    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.http.HttpMethod;
    import org.springframework.security.config.annotation.web.builders.HttpSecurity;
    import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
    import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
    import org.springframework.security.config.http.SessionCreationPolicy;
    import org.springframework.security.core.Authentication;
    import org.springframework.security.core.AuthenticationException;
    import org.springframework.security.web.authentication.AuthenticationFailureHandler;
    import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
    import org.springframework.security.web.session.SessionManagementFilter;

    import com.fasterxml.jackson.databind.ObjectMapper;

    @Configuration
    @EnableWebSecurity
    public class BasicSecurityAdapter extends WebSecurityConfigurerAdapter {

        @Autowired
        private AuthEntryPoint authEntryPoint;

        @Autowired
        private CustomAuthenticationProvider customAuthProvider;

        @Autowired
        private CorsFilter corsFilter;

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http.csrf().disable();
            http.addFilterBefore( corsFilter, SessionManagementFilter.class );
            http.headers().frameOptions().disable();
            http.httpBasic().authenticationEntryPoint( authEntryPoint );
            http.authenticationProvider( customAuthProvider );

            http
                    .authorizeRequests()
                    .antMatchers( "/public/**" ).permitAll()
                    .antMatchers( HttpMethod.OPTIONS, "/**" ).permitAll()
                    .antMatchers( "/**" ).hasAuthority( "protected" )
                    .anyRequest().authenticated()
                    .and()
                    .sessionManagement()
                    .sessionCreationPolicy( SessionCreationPolicy.STATELESS );

            http.formLogin().loginProcessingUrl( "/login" );

            http.formLogin().successHandler( new AuthenticationSuccessHandler() {
                @Override
                public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                        Authentication authentication) throws IOException, ServletException {
                    response.addHeader( "Access-Control-Expose-Headers", "role" );
                    response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                    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-Expose-Headers", "Content-Disposition,X-Suggested-Filename,X-Auth-Token,Authorization" );
                    response.setHeader( "Access-Control-Max-Age", "180" );
                    String role = authentication.getAuthorities().stream().findFirst().get().toString();
                    response.addHeader( "role", role );

                    ObjectMapper objectMapper = new ObjectMapper();
                    Map<String, Object> data = new HashMap<String, Object>();
                    data.put( "role", role );
                    response.getWriter().print( objectMapper.writeValueAsString( data ) );
                }
            } );

            http.formLogin().failureHandler( new AuthenticationFailureHandler() {
                @Override
                public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException exception) throws IOException, ServletException {
                    response.addHeader( "Access-Control-Expose-Headers", "role" );
                    response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                    response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                    response.setHeader( "Access-Control-Allow-Headers", "*" );
                    response.setHeader( "Content-type", "application/json; charset=UTF-8" );
                    response.setCharacterEncoding( "UTF-8" );
                    response.setStatus( HttpServletResponse.SC_UNAUTHORIZED );

                    ObjectMapper objectMapper = new ObjectMapper();
                    Map<String, String> data = new HashMap<String, String>();
                    data.put( "message", exception.getMessage() );
                    response.getWriter().print( objectMapper.writeValueAsString( data ) );
                }
            } );

            http.logout().logoutSuccessHandler( (request, response, authentication) -> {
                response.setHeader( "Access-Control-Allow-Origin", request.getHeader( "Origin" ) );
                response.setHeader( "Access-Control-Allow-Methods", "GET,POST,DELETE,PUT,OPTIONS" );
                response.setHeader( "Access-Control-Allow-Headers", "*" );
                response.setStatus( HttpServletResponse.SC_OK );
            } );
        }

    }

My use case

In my scenario I store the user login and password on a cookie when user log in and I carry those credentials when calling the API.

I believe that you can change from basic auth to another option trying to change the CustomAuthenticationProvider.

Maybe a little late for you, but I hope this helps somebody.