2

I have a single page app developed with Facebook's React framework, where I use this bit of JSX to determine whether or not the user is logged in:

render: function() {
    return <div>
      <BrandBar/>
      <div className="container">
        <TopBar/>
        {this.state.sessionState == 'LOADING' ? (<h1>Loading</h1>) : (this.state.sessionState == 'LOGGEDIN' ? this.props.children : <Login/>)}
      </div>
    </div>
}

The login function which alters the state for this component is a simple REST call (using superagent) that fetches the user info with basic auth credentials attached:

login: function(username, password){
    return {
      then: function(callback){
        request 
          .get(rootUrl + 'me')
          .auth(username, password)
          .end((err, res) => {
            callback(res);
          });
      }
   }
}

On the backend side, I have a REST api in Spring Boot:

Controller:

@RequestMapping(value = "/me",
        produces = {APPLICATION_JSON},
        method = RequestMethod.GET)
public User getMe() {
    Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
    if (principal != null && principal instanceof CurrentUser){
        return ((CurrentUser) principal).getUser();
    }

    return null;
}

@RequestMapping("/stop")
@ResponseStatus(HttpStatus.NO_CONTENT)
public void stop(HttpSession session) {
    session.invalidate();
}

Security config:

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private UserDetailsService userDetailsService;

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

        http.csrf().disable()
                .httpBasic().and()
                .authorizeRequests().anyRequest().authenticated()
                .and().anonymous().disable()
                .exceptionHandling().authenticationEntryPoint(new BasicAuthenticationEntryPoint(){
            @Override
            public void commence(final HttpServletRequest request, final HttpServletResponse response, final AuthenticationException authException) throws IOException, ServletException {
                response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
            }
        });

    }

    @Override
    public void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth
                .userDetailsService(userDetailsService)
                .passwordEncoder(new BCryptPasswordEncoder());
    }

}

Logging in works fine as long as the info I provide is correct, but when I supply bad credentials, the server bypasses the exception handling that returns a plain 401 (without the WWW-Authenticate header). So the browser shows me the popup despite the overridden BasicAuthenticationEntryPoint.

What's even more weird is, when I provide the correct details in the browser popup, it still returns a 401 status.

This seems like a basic setup though: a simple REST service with Spring Security and a single page app on front-end. Has anyone had a similar issue?

Anthony De Smet
  • 2,265
  • 3
  • 17
  • 24
  • Try to create a separate login page, it's much easier to implement: it will contain your component and should be open, i.e. permitAll(). The app page should be "authenticated()". With this approach your this.state.sessionState == 'LOGGEDIN' logic became redundant. – Grigory Katkov Oct 21 '15 at 22:57
  • I was using this approach earlier, but I feel this makes for cleaner code since I don't need to implement a mechanism to track which url the user was trying to visit. If the user wants to go to https://domain/task/id and needs to log in first, with this approach it works automatically: login screen shown first, and then the target page. My problem is solely with the authentication request. – Anthony De Smet Oct 22 '15 at 10:48
  • Well, maybe I don't clearly understand your goal, but I would rather concentrate on that issue with preserving target url, spring security allows you to do that. See [this](http://stackoverflow.com/questions/14573654/spring-security-redirect-to-previous-page-after-succesful-login). And, by the way, keeping auth logic on client side gives you a kind of 'fake' authorization, because you can authenticate yourself via js debugger in browser. Cheers! – Grigory Katkov Oct 22 '15 at 13:44
  • What version of Spring Security / Spring Boot are you using? – Rob Winch Oct 22 '15 at 15:31

2 Answers2

1

Have you already tried to register an access denied handler ?

http.exceptionHandling().accessDeniedHandler(new AccessDeniedHandlerImpl());

public class AccessDeniedHandlerImpl implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest request, HttpServletResponse     response, AccessDeniedException accessDeniedException) throws IOException, ServletException {
        response.sendError(HttpStatus.UNAUTHORIZED.value());
    }
}

just as a test try to throw an AuthenticationException when you don't find the principal in the security context (the code inside the controller) to see if somewhere in the filter chain the exception is handled, it should be.

Emanuele Ivaldi
  • 1,332
  • 10
  • 15
1

If you don't want the browser popup, you better avoid the 401.

For AJAX, I suggest to use 403 for authentication entry point instead.

response.sendError(HttpServletResponse.SC_FORBIDDEN)

If you need to distingish between "real" forbidden (when user is authenticated but not allowed to access) and above, you can pass information in response body or as a custom header.

holmis83
  • 15,922
  • 5
  • 82
  • 83