Authentication Interview Questions & Answers
Authentication in Spring Security — UserDetailsService and UserDetails, AuthenticationManager and providers, DaoAuthenticationProvider, form login vs HTTP Basic, custom authentication, the Authentication object, and logout.
An authentication filter builds an unauthenticated Authentication (e.g. username + password)
and hands it to the AuthenticationManager, which delegates to one or more
AuthenticationProviders. A provider verifies the credentials and returns a fully populated,
authenticated Authentication that's stored in the SecurityContext.
Filter → AuthenticationManager → AuthenticationProvider
→ UserDetailsService.loadUserByUsername()
→ PasswordEncoder.matches()
→ authenticated Authentication → SecurityContext
For username/password the default provider is DaoAuthenticationProvider, which loads the user via
UserDetailsService and compares the password with the PasswordEncoder. On success the principal
and authorities are available everywhere for the rest of the request.
Rule of thumb: Authentication flows filter → manager → provider → user store; the result is an
authenticated Authentication placed in the SecurityContext.
UserDetailsService is the single-method interface Spring Security calls to load a user by
username. You implement it to fetch from your database (or any store) and return a UserDetails.
@Service
class JpaUserDetailsService implements UserDetailsService {
private final UserRepository users;
public UserDetails loadUserByUsername(String username) {
AppUser u = users.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException(username));
return User.withUsername(u.getUsername())
.password(u.getPasswordHash()) // already-encoded hash
.authorities(u.getRoles().toArray(String[]::new))
.build();
}
}
Defining this bean replaces the default in-memory user. It returns the stored hash — Spring
compares it via the PasswordEncoder; you never decode the password.
Rule of thumb: Implement UserDetailsService.loadUserByUsername to bridge your user table to
Spring Security; return the stored hash and let the encoder verify it.
UserDetails is Spring Security's view of a user: its username, (hashed) password,
authorities, and account-status flags (enabled, locked, expired). The framework only ever talks
to this interface, decoupling it from your entity.
public interface UserDetails {
String getUsername();
String getPassword();
Collection<? extends GrantedAuthority> getAuthorities();
boolean isEnabled();
boolean isAccountNonLocked();
// ...
}
You can return the built-in User implementation or make your own entity implement
UserDetails. The status flags let Spring reject disabled or locked accounts automatically.
Rule of thumb: UserDetails is the contract Spring needs — username, hash, authorities, status
flags — supplied by the built-in User or your own class.
The AuthenticationManager is the entry point that attempts authentication; the standard
implementation, ProviderManager, holds a list of AuthenticationProviders and tries each until
one can handle the token. A provider does the actual verification for a credential type.
@Bean
AuthenticationManager authManager(UserDetailsService uds, PasswordEncoder pe) {
var provider = new DaoAuthenticationProvider();
provider.setUserDetailsService(uds);
provider.setPasswordEncoder(pe);
return new ProviderManager(provider);
}
Multiple providers let one manager support several mechanisms (username/password, LDAP, tokens). A
provider's supports() declares which Authentication type it accepts.
Rule of thumb: AuthenticationManager orchestrates; AuthenticationProviders do the per-mechanism
verification — add a provider to support a new credential type.
DaoAuthenticationProvider is the default provider for username/password auth. It loads the user
through a UserDetailsService, verifies the raw password against the stored hash using the
PasswordEncoder, checks the account-status flags, and on success produces an authenticated token.
// conceptually:
UserDetails u = userDetailsService.loadUserByUsername(name);
if (!passwordEncoder.matches(rawPassword, u.getPassword()))
throw new BadCredentialsException("Bad credentials");
if (!u.isEnabled()) throw new DisabledException("disabled");
In Boot you often don't create it explicitly — defining a UserDetailsService and PasswordEncoder
bean is enough for auto-configuration to wire one up.
Rule of thumb: DaoAuthenticationProvider ties UserDetailsService + PasswordEncoder together
for password login; supply those two beans and it's configured for you.
Form login presents an HTML login page, submits credentials as a POST, and stores authentication
in a session — suited to browser, server-rendered apps. HTTP Basic sends
Authorization: Basic base64(user:pass) on every request — simple, stateless, suited to APIs and
tooling.
http.formLogin(withDefaults()); // browser login page + session
// or
http.httpBasic(withDefaults()); // credentials on every request header
Basic must run over HTTPS (credentials are only base64-encoded, not encrypted) and has no logout concept. Form login gives a real UX and session lifecycle. APIs usually prefer Basic for machine clients or a token scheme over either.
Rule of thumb: Form login (session) for browser apps; HTTP Basic (per-request header, HTTPS only) for simple API/machine access.
Inject the principal as a method parameter — the cleanest options are @AuthenticationPrincipal for
the UserDetails/principal, or an Authentication/Principal argument. Avoid reaching into
SecurityContextHolder from controllers.
@GetMapping("/me")
String me(@AuthenticationPrincipal UserDetails user) {
return user.getUsername();
}
@GetMapping("/whoami")
String who(Authentication auth) {
return auth.getName() + " " + auth.getAuthorities();
}
@AuthenticationPrincipal can even bind your custom principal type directly. These are resolved per
request from the SecurityContext, so they always reflect the current user.
Rule of thumb: Read the current user via @AuthenticationPrincipal or an Authentication
parameter — let Spring inject it instead of touching the holder.
For demos, tests, or a tiny fixed user set, expose an InMemoryUserDetailsManager bean with users
built via User.withUsername(...). Passwords must still be encoded.
@Bean
UserDetailsService users(PasswordEncoder encoder) {
UserDetails admin = User.withUsername("admin")
.password(encoder.encode("secret"))
.roles("ADMIN") // becomes ROLE_ADMIN
.build();
return new InMemoryUserDetailsManager(admin);
}
It's perfect for examples and integration tests but not for production — the users live in memory
and vanish on restart. Swap in a JPA-backed UserDetailsService for real apps.
Rule of thumb: Use InMemoryUserDetailsManager for tests and demos with encoded passwords; never
ship it as your production user store.
When credentials don't fit username/password (an API key, an external SSO, a one-time code), implement
AuthenticationProvider: authenticate() does the verification, supports() declares the token type
it handles.
@Component
class ApiKeyAuthProvider implements AuthenticationProvider {
public Authentication authenticate(Authentication a) {
String key = (String) a.getCredentials();
if (!keyService.isValid(key)) throw new BadCredentialsException("bad key");
return new ApiKeyAuthenticationToken(key, keyService.authorities(key)); // authenticated
}
public boolean supports(Class<?> type) {
return ApiKeyAuthenticationToken.class.isAssignableFrom(type);
}
}
Register it with the AuthenticationManager (or as a bean Boot picks up). Return an authenticated
token on success and throw an AuthenticationException on failure.
Rule of thumb: Implement AuthenticationProvider for non-standard credentials — verify in
authenticate(), gate it with supports(), and return an authenticated token.
By default DaoAuthenticationProvider hides whether the username was unknown or the password was
wrong, converting UsernameNotFoundException into BadCredentialsException. This prevents username
enumeration — an attacker can't probe which accounts exist.
// hideUserNotFoundExceptions = true (default)
// unknown user AND wrong password both surface as "Bad credentials"
It also runs the password check against a dummy hash for unknown users so the response time is similar, defeating timing attacks. Keep this default on in production.
Rule of thumb: A uniform "Bad credentials" for both unknown user and wrong password is intentional — it stops attackers from discovering valid usernames.
UserDetails carries four status flags — isEnabled, isAccountNonLocked, isAccountNonExpired,
isCredentialsNonExpired. The provider checks them after the password matches and throws a specific
exception if any fails, so a correct password still can't log in a disabled account.
return User.withUsername(u.getName())
.password(u.getHash())
.disabled(!u.isActive()) // → DisabledException
.accountLocked(u.isLocked()) // → LockedException
.build();
This lets you implement lockout, deactivation, and forced password resets declaratively rather than with ad-hoc checks in controllers.
Rule of thumb: Model account state with the UserDetails status flags — Spring enforces them
automatically after the password check.
Spring Security publishes application events — AuthenticationSuccessEvent,
AbstractAuthenticationFailureEvent — that you handle with @EventListener. This is the clean place
for audit logging, failed-attempt counting, or lockout.
@Component
class LoginAudit {
@EventListener
void onFailure(AbstractAuthenticationFailureEvent e) {
log.warn("Login failed for {}", e.getAuthentication().getName());
}
@EventListener
void onSuccess(AuthenticationSuccessEvent e) {
log.info("Login OK for {}", e.getAuthentication().getName());
}
}
Events keep audit concerns out of the authentication flow itself. For custom redirect/JSON
responses use an AuthenticationSuccessHandler/AuthenticationFailureHandler instead.
Rule of thumb: Listen for authentication events to audit logins and drive lockout; use success/ failure handlers when you need to shape the HTTP response.
With form login, Spring auto-registers a /logout endpoint (POST by default with CSRF on) that
invalidates the session, clears the SecurityContext, and removes the remember-me/session cookie.
http.logout(logout -> logout
.logoutUrl("/logout")
.logoutSuccessUrl("/?bye")
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true));
For stateless token APIs there's nothing server-side to invalidate — "logout" means the client discards the token, optionally backed by a server-side deny-list of revoked tokens until they expire.
Rule of thumb: Session logout invalidates the session and clears the context; stateless logout is the client dropping the token (plus an optional revocation list).
Remember-me keeps a user logged in across sessions via a long-lived cookie, so they aren't forced to re-authenticate after the session expires. Spring offers a hashed-token cookie or a persistent-token variant backed by a database table.
http.rememberMe(rm -> rm
.key("a-strong-secret")
.tokenValiditySeconds(1209600)); // 14 days
Remember-me grants a weaker authentication level — you can require full authentication
(fullyAuthenticated()) for sensitive actions while allowing remember-me elsewhere. Persistent tokens
add rotation and theft detection.
Rule of thumb: Remember-me trades a long-lived cookie for convenience; treat it as lower-trust and demand full re-authentication for sensitive operations.
An Authentication exposes three things: the principal (who — usually the UserDetails or a
username), the credentials (proof — the password/token, typically erased after authentication), and
the authorities (what — the granted roles/permissions).
Authentication a = SecurityContextHolder.getContext().getAuthentication();
Object principal = a.getPrincipal(); // UserDetails or username
Object credentials = a.getCredentials(); // null after successful auth
var authorities = a.getAuthorities(); // ROLE_USER, ...
a.isAuthenticated(); // true once verified
After successful authentication the credentials are usually cleared so a password never lingers in memory. Authorization rules read the authorities.
Rule of thumb: Principal = identity, credentials = proof (erased post-login), authorities = permissions — together they make up the authenticated token.
More Security interview questions
More ways to practice
The self-quiz is live. Join our channel for updates, new content & tech tips.