Spring Boot security shows Http-Basic-Auth popup after failed login

Let's start with your problem

It is not a "Spring Boot security popup" its a browser popup that shows up, if the response of your Spring Boot app contains the following header:

WWW-Authenticate: Basic

In your security configuration a .formLogin() shows up. This should not be required. Though you want to authenticate through a form in your AngularJS application, your frontend is an independent javascript client, which should use httpBasic instead of form login.

How your security config could look like

I removed the .formLogin() :

@Override
protected void configure(HttpSecurity http) throws Exception {
    http
            .httpBasic()
                .and()
            .authorizeRequests()
            .antMatchers("/user", "/index.html", "/", "/projects/listHome", "/projects/{id}", "/categories", "/login").permitAll().anyRequest()
            .authenticated()
                .and()
            .csrf().csrfTokenRepository(csrfTokenRepository())
                .and()
            .addFilterAfter(csrfHeaderFilter(), CsrfFilter.class);
}

How to deal with the browser popup

As previously mentioned the popup shows up if the response of your Spring Boot app contains the header WWW-Authenticate: Basic. This should not be disabled for all requests in your Spring Boot app, since it allows you to explore the api in your browser very easily.

Spring Security has a default configuration that allows you to tell the Spring Boot app within each request not to add this header in the response. This is done by setting the following header to your request:

X-Requested-With: XMLHttpRequest

How to add this header to every request made by your AngularJS app

You can just add a default header in the app config like that:

yourAngularApp.config(['$httpProvider',
  function ($httpProvider) {
    $httpProvider.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
  }
]);

The backend will now respond with a 401-response that you have to handle by your angular app (by an interceptor for example).

If you need an example how to do this you could have a look at my shopping list app. Its done with spring boot and angular js.


As Yannic Klem already said this is happening because of the header

WWW-Authenticate: Basic

But there is a way in spring to turn it off, and it's really simple. In your configuration just add:

.httpBasic()
.authenticationEntryPoint(authenticationEntryPoint)

and since authenticationEntryPoint is not defined yet, autowire it at the beginning:

@Autowired private MyBasicAuthenticationEntryPoint authenticationEntryPoint;

And now create MyBasicAuthenticationEntryPoint.class and paste the following code:

import java.io.IOException;
import java.io.PrintWriter;

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

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.www.BasicAuthenticationEntryPoint;
import org.springframework.stereotype.Component;

@Component
public class MyBasicAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

/**
 * Used to make customizable error messages and codes when login fails
 */
@Override
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authEx) 
  throws IOException, ServletException {
    response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
    PrintWriter writer = response.getWriter();
    writer.println("HTTP Status 401 - " + authEx.getMessage());
}

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

Now your app wont send WWW-Authenticate: Basic header, because of that pop-up windows will not show, and there is no need to mess with headers in Angular.


As already explained above, the problem is in the header of response that is set with the values "WWW-Authenticate: Basic".

Another solution that can solve this is to implement the AuthenticationEntryPoint interface (directly) without placing these values in the header:

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    //(....)

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

        http
        .csrf()
            .disable()
        .authorizeRequests()
            .antMatchers("/*.css","/*.js","/*.jsp").permitAll()
            .antMatchers("/app/**").permitAll()
            .antMatchers("/login").permitAll()

            .anyRequest().authenticated()

            .and()
                .formLogin()
                .loginPage("/login")
                .loginProcessingUrl("/j_spring_security_check")
                .defaultSuccessUrl("/", true)
                .failureUrl("/login?error=true")
                .usernameParameter("username")
                .passwordParameter("password")
                .permitAll()
            .and()
                .logout()
                .logoutUrl("/logout")
                .logoutSuccessUrl("/login")
                .deleteCookies("JSESSIONID")
                .clearAuthentication(true)
                .invalidateHttpSession(true)
            .and()
                .exceptionHandling()
                .accessDeniedPage("/view/error/forbidden.jsp")
            .and()
                .httpBasic()
                .authenticationEntryPoint(new AuthenticationEntryPoint(){ //<< implementing this interface
                    @Override
                    public void commence(HttpServletRequest request, HttpServletResponse response,
                        AuthenticationException authException) throws IOException, ServletException {
                            //>>> response.addHeader("WWW-Authenticate", "Basic realm=\"" + realmName + "\""); <<< (((REMOVED)))
                            response.sendError(HttpStatus.UNAUTHORIZED.value(), HttpStatus.UNAUTHORIZED.getReasonPhrase());
                    }
                });
    }

    //(....)
}