Custom Authentication

About

Custom Authentication in Spring Security allows developers to define custom logic for verifying user credentials instead of using the default authentication mechanisms like Basic Authentication, Form-Based Authentication, JWT, or OAuth2.

This is useful when:

  • We have a custom user store (e.g., database, LDAP, API, or third-party service).

  • We need additional validation beyond username and password (e.g., checking user status, roles, OTP, or security questions).

  • We require a non-standard authentication flow (e.g., token-based or biometric authentication).

How Does Custom Authentication Work?

Spring Security authentication follows a step-by-step process. When customizing authentication, we typically extend or override one or more of the following components:

Core Components in Custom Authentication

Component

Role in Authentication

AuthenticationManager

Main interface that delegates authentication requests.

AuthenticationProvider

Custom logic to validate user credentials.

UserDetailsService

Loads user-specific data from a database or API.

UserDetails

Represents authenticated user details.

PasswordEncoder

Hashes passwords for secure authentication.

SecurityFilterChain

Configures authentication mechanisms and filters.

Implementing Custom Authentication in Spring Security

To create a custom authentication mechanism, we typically:

  1. Implement a Custom AuthenticationProvider.

  2. Implement a Custom UserDetailsService (if needed).

  3. Configure authentication inside SecurityFilterChain.

Step-by-Step Implementation of Custom Authentication

Step 1: Implement a Custom Authentication Provider

A Custom Authentication Provider replaces Spring Security’s default authentication logic.

@Component
public class CustomAuthenticationProvider implements AuthenticationProvider {

    @Autowired
    private UserDetailsService userDetailsService; // Loads user from database

    @Autowired
    private PasswordEncoder passwordEncoder; // Hashes password

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = authentication.getCredentials().toString();

        // Load user details from DB or any external source
        UserDetails userDetails = userDetailsService.loadUserByUsername(username);

        // Validate password
        if (!passwordEncoder.matches(password, userDetails.getPassword())) {
            throw new BadCredentialsException("Invalid username or password");
        }

        // If authentication is successful, return an authenticated token
        return new UsernamePasswordAuthenticationToken(
            userDetails, password, userDetails.getAuthorities()
        );
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
    }
}
  • Loads user details using UserDetailsService.

  • Compares passwords using PasswordEncoder.

  • Returns an authenticated token upon success.

Step 2: Implement a Custom UserDetailsService (If Needed)

If user details need to be fetched from a database, API, or another source, implement UserDetailsService.

@Service
public class CustomUserDetailsService implements UserDetailsService {

    @Autowired
    private UserRepository userRepository; // Custom JPA repository

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User user = userRepository.findByUsername(username)
                .orElseThrow(() -> new UsernameNotFoundException("User not found"));

        return new org.springframework.security.core.userdetails.User(
                user.getUsername(), 
                user.getPassword(), 
                user.getAuthorities()
        );
    }
}
  • Fetches user details from a database.

  • Converts User entity into UserDetails.

Step 3: Configure SecurityFilterChain

Finally, register the Custom Authentication Provider inside SecurityFilterChain.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private CustomAuthenticationProvider customAuthenticationProvider;

    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeHttpRequests(auth -> auth
                .anyRequest().authenticated()
            )
            .authenticationProvider(customAuthenticationProvider) // Register custom authentication
            .formLogin(withDefaults());

        return http.build();
    }
}
  • Registers CustomAuthenticationProvider inside Spring Security.

  • Uses Form-Based Login (can be replaced with another authentication method).

Handling Custom Authentication Logic

We can modify the authentication logic based on:

Scenario

Customization

Custom User Store (e.g., NoSQL, External API, LDAP, etc.)

Modify UserDetailsService to load users from a different source.

Custom Password Hashing

Replace PasswordEncoder with a custom implementation.

Multi-Factor Authentication (MFA)

Extend AuthenticationProvider to verify OTPs or security questions.

Biometric Authentication

Integrate with third-party biometric authentication services.

When to Use Custom Authentication?

Scenario

Why Custom Authentication?

Using a custom user store (API, NoSQL, etc.)

Spring’s default authentication only supports JDBC/LDAP.

Enforcing additional security measures

Custom validations like OTP, security questions, or biometric login.

Building a REST API with JWT authentication

Stateless authentication for APIs.

Custom login flows (e.g., SSO, social login, third-party auth)

Default mechanisms may not support complex authentication logic.

Last updated

Was this helpful?