How to prevent basic auth pop up for invalid credentials in Spring Boot REST API?

Let’s say you have created a backend REST API application in Spring Boot.

Your front end application is deployed separately and it communicates with the backend via REST API calls.

You decide to protect the backend with Spring Security.

You also decide to use inbuilt Spring mechanism to validate login.

So to authenticate a user you choose to use a REST API method with “Principal” object as reference.

Something like this:

	@GetMapping("/login")
	public Principal login(Principal principal) {
		
		
		return principal;
	}

This method will automatically validate basic auth credentials. The returned principal object will contain the details of whether the user is expired / locked etc too.

To test this , I used in memory authentication using hardcoded password like this:

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
 
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
 
        auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("USER");
 
    }
 
    @Override
    public void configure(HttpSecurity http) throws Exception {
 
        http.authorizeRequests().anyRequest().authenticated().and().httpBasic();
    }
 
}

You can configure credentials in your database or LDAP by configuring the authentication manager. In this example though there is a single credential with user name “admin” and password “admin”

Now on calling the REST API /login I got the below response:

This feature provided by Spring Security is quite handy as you don’t have to authenticate the credentials yourself and also you get to know if the user is locked or expired too.

Also it returns a 401 error code if the credentials are invalid.

And here comes a problem.

Your browser will show a basic auth popup for the user to enter the correct username and password if invalid credentials are entered.

But say you don’t want that.

You just want to show an error message in your front end (for example say Angular application).

Why does this popup appear?

And how do you prevent it?

First why does the popup appear?

This is because of a HTTP header!

“WWW-Authenticate” is the black sheep here.

Spring Security follows HTTP protocol.

And HTTP authentication mandates that the server return a 401 error code along with the header “WWW-Authenticate”.

Here is the definition from Mozilla docs:

A server using HTTP authentication will respond with a 401 Unauthorized response to a request for a protected resource. This response must include at least one WWW-Authenticate header and at least one challenge, to indicate what authentication schemes can be used to access the resource (and any additional data that each particular scheme needs).

And thus Spring returns a header “WWW-Authenticate” with a value like “Basic realm=’Realm’” to indicate the browser that the credentials are invalid and it should prompt the user to enter basic auth credentials again.

But what if the front end will choose how to prompt the user to enter valid credentials?

How can this be implemented in Spring ?

By providing your own “BasicAuthenticationEntryPoint” class

Here is a sample class:

package app.example;

import java.io.IOException;

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

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

public class CustomAuthenticationEntryPoint extends BasicAuthenticationEntryPoint {

	public CustomAuthenticationEntryPoint() {
		this.setRealmName("myown");
	}

	@Override
	public void commence(HttpServletRequest request, HttpServletResponse response,
			AuthenticationException authException) throws IOException {
		response.setHeader("WWW-Authenticate", "myown");
		response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
	}
}

I am providing my own WWW-Authenticate header through the above class.

Once you define this class you need to add it in Security Configuration:

package app.example;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
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.web.AuthenticationEntryPoint;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {

		auth.inMemoryAuthentication().withUser("admin").password("{noop}admin").roles("USER");

	}

	@Override
	public void configure(HttpSecurity http) throws Exception {
		AuthenticationEntryPoint entryPoint = new CustomAuthenticationEntryPoint();

		http.authorizeRequests().anyRequest().authenticated().and().httpBasic().authenticationEntryPoint(entryPoint);
	}

}

Notice that I have added the authentication entry class in configure() method after httpBasic() method call.

Once this is done when invalid credentials are entered from front end , the header WWW-Authenticate is sent with your own custom value.

And the browser stops showing the popup!

And then the UI can decide how to handle the error.

The browser didn’t show the basic popup in the above case and it the front end’s responsibility now to send the basic auth credentials and handle invalid credentials.

That’s it!

Here is the code link:

https://github.com/vijaysrj/basicauthpopup


Posted

in

,

by

Comments

Leave a Reply

Discover more from The Full Stack Developer

Subscribe now to keep reading and get access to the full archive.

Continue reading