How do you implement a custom AuthenticationProvider? What must supports() and authenticate() do?
answer
- supports(Class) = which token types
- authenticate = verify -> new authenticated token
- throw BadCredentials/AccountStatus, return null if undecided
- never mutate & return the input token
- register via http.authenticationProvider(...)
basics
~20 sImplement AuthenticationProvider with two methods. supports(Class) returns true for the token types you handle. authenticate(Authentication) validates the credentials and returns a new, fully authenticated token with authorities, throws an AuthenticationException on failure, or returns null if it can't handle the request.
solid answer
~40 sImplement the AuthenticationProvider SPI. supports(Class<?>) declares which Authentication subtypes you handle — e.g. return true only for your ApiKeyAuthenticationToken; ProviderManager uses this to skip you for irrelevant tokens. authenticate(Authentication) does the real work: extract the principal/credentials, verify them (look up a user, compare a hashed password, validate a token), and on success build and return a NEW authenticated token — typically via the constructor that takes authorities, which sets isAuthenticated() true. On bad credentials throw BadCredentialsException; for account state throw the appropriate AccountStatusException. Return null only if you decide you can't process the request after all. Register it as a bean and add it to the ProviderManager (in modern config via AuthenticationManagerBuilder.authenticationProvider(...) or by building the ProviderManager yourself). Never mutate and return the incoming token as authenticated.
code
java · 24 lines@Component
class ApiKeyAuthenticationProvider implements AuthenticationProvider {
private final ApiKeyService apiKeys;
ApiKeyAuthenticationProvider(ApiKeyService apiKeys) { this.apiKeys = apiKeys; }
@Override
public Authentication authenticate(Authentication authentication) {
String rawKey = (String) authentication.getCredentials();
ApiClient client = apiKeys.findByKey(rawKey)
.orElseThrow(() -> new BadCredentialsException("Invalid API key"));
if (!client.isEnabled()) {
throw new DisabledException("API client disabled"); // fatal: stops the chain
}
// Build a NEW, authenticated token (this ctor sets authenticated = true)
return new ApiKeyAuthenticationToken(client, rawKey, client.getAuthorities());
}
@Override
public boolean supports(Class<?> authentication) {
return ApiKeyAuthenticationToken.class.isAssignableFrom(authentication);
}
}go deeper
Know the two methods and that authenticate returns an authenticated token or throws.
Correctly build a new token with authorities, throw the right exceptions, and register the provider.
Discuss credential erasure, timing/enumeration defenses, and when to extend AbstractUserDetailsAuthenticationProvider.
Weigh provider composition, ordering, fatal-exception semantics, and reuse of built-in tokens for CredentialsContainer behavior across a multi-mechanism auth surface.
## The SPI you implement ```java public interface AuthenticationProvider { Authentication authenticate(Authentication authentication) throws AuthenticationException; boolean supports(Class<?> authentication); } ``` An `AuthenticationProvider` encapsulates **one way to authenticate**. `ProviderManager` owns a list of them and delegates. ## supports(Class) - Receives the **class of the incoming `Authentication`** (not an instance). - Return `true` for the token types this provider knows how to process. `DaoAuthenticationProvider`, for example, returns `true` for `UsernamePasswordAuthenticationToken` (and assignable subclasses via `isAssignableFrom`). - If it returns `false`, `ProviderManager` **skips** the provider entirely — `authenticate()` is never called for that token. ## authenticate(Authentication) The method must do three things depending on outcome: 1. **Success**: verify the credentials, then **construct and return a new, authenticated `Authentication`**. Use the constructor that accepts a `Collection<? extends GrantedAuthority>` — e.g. `new UsernamePasswordAuthenticationToken(principal, credentials, authorities)`. That constructor internally calls `setAuthenticated(true)`. The returned principal is often a `UserDetails`. 2. **Failure (bad credentials)**: throw `BadCredentialsException`. For account state, throw the specific `AccountStatusException` subtype (`DisabledException`, `LockedException`, `AccountExpiredException`, `CredentialsExpiredException`). For infrastructure failures wrap in `InternalAuthenticationServiceException` — remember both of these families short-circuit the `ProviderManager` chain. 3. **Undecided**: return `null` if, after inspecting the token, you determine you can't handle it. In practice `supports()` usually already filters this out. ## Registration Modern Spring Security (6.x): ```java @Bean SecurityFilterChain chain(HttpSecurity http, MyProvider provider) throws Exception { http.authenticationProvider(provider) /* ... */; return http.build(); } ``` or build the `ProviderManager` directly and expose it as the `AuthenticationManager` bean. `http.authenticationProvider(...)` registers it into the `AuthenticationManagerBuilder` for that chain. ## Gotchas & best practices - **Return a new token, don't reuse the input.** The incoming token is an untrusted request; mutating its `isAuthenticated` flag and returning it is a security smell. Build a fresh authenticated instance. - **Erase credentials awareness**: if your token implements `CredentialsContainer`, `ProviderManager` will erase the credentials after success (default). Extend the built-in tokens to get this for free. - **Timing/enumeration**: like `DaoAuthenticationProvider`, avoid leaking whether a username exists — throw `BadCredentialsException` uniformly (Spring even runs a dummy password check to equalize timing; `hideUserNotFoundExceptions` defaults to true). - **Consider extending `AbstractUserDetailsAuthenticationProvider`** instead of implementing from scratch when doing username/password style auth — it provides caching, pre/post checks, and correct exception handling. - **Ordering**: if multiple providers support the same token type, list order in `ProviderManager` decides who runs first.
- Why should authenticate() build a new token instead of calling setAuthenticated(true) on the input?The input token is an untrusted authentication request. Constructing a new token via the authorities-constructor is the idiomatic, safe way to produce a trusted authenticated result; mutating the request blurs the request/result distinction and risks accidentally trusting caller-supplied flags.
- When would you extend AbstractUserDetailsAuthenticationProvider instead of implementing AuthenticationProvider directly?For username/password authentication backed by a UserDetailsService. It gives you user caching, pre/post authentication checks (enabled/locked/expired), consistent exception hiding, and a template method (additionalAuthenticationChecks) so you only implement the credential comparison.
saying these in an interview costs you the question
- Calling setAuthenticated(true) on the incoming token and returning it
- Returning null on bad credentials instead of throwing
- supports() returning true for all types unconditionally
- Leaking user-existence via distinct UsernameNotFound vs BadCredentials messages