Appearance
Spring Security Part 4: Basic Authentication (Stateless)
Form login (part 3) keeps a session on the server. The next methods are stateless: the server remembers nothing between requests, so any server instance can handle any request. The simplest is Basic authentication.
Basic authentication
Basic authentication is stateless: the server keeps no authentication state, no session. So the client sends the username and password with every request, in the Authorization header:
http
Authorization: Basic <base64(username:password)>For user / pass:
http
Authorization: Basic dXNlcjpwYXNzBase64 is an encoding, not encryption. Anyone who sees the header can decode dXNlcjpwYXNz back to user:pass instantly. Over plain HTTP the credentials are exposed, so use basic authentication only over HTTPS.
A user for testing
Any of the ways from part 2 works; for testing:
properties
spring.security.user.name=user
spring.security.user.password=pass
spring.security.user.roles=ADMIN,USERReading the header
BasicAuthenticationFilter reads and decodes the header:
java
// BasicAuthenticationConverter.convert, simplified
String header = request.getHeader(HttpHeaders.AUTHORIZATION); // read the Authorization header
if (header == null) {
return null; // no basic authentication here
}
header = header.trim();
if (!StringUtils.startsWithIgnoreCase(header, "Basic")) {
return null;
}
if (header.equalsIgnoreCase("Basic")) {
throw new BadCredentialsException("Empty basic authentication token");
}
byte[] base64Token = header.substring(6).getBytes(StandardCharsets.UTF_8);
byte[] decoded = decode(base64Token); // decode username:password
String token = new String(decoded, getCredentialsCharset(request));
int delim = token.indexOf(":");
// username = before ':', password = after itWhy a header, and not the request body?
- Standard. RFC 7617 defines this format, so every API and client understands it. If some APIs wanted credentials in the body and others in headers, working with many APIs and clients would be painful.
- Less exposure in logs. Web servers and tools sometimes log request bodies (and query parameters) for debugging or analytics, sometimes into third-party logging systems. The
Authorizationheader is widely treated as sensitive and is normally kept out of such logs. (Not guaranteed: check what your own logging captures.) - Works for every method.
GETrequests have no body; a header works the same forGET,POST,PUTandDELETE.
The flow
It is form login's flow squeezed into one request: authentication and authorization happen together, and no session is created.
BasicAuthenticationFilter decodes the header, builds a UsernamePasswordAuthenticationToken and passes it to the ProviderManager; DaoAuthenticationProvider loads the user and checks the password, exactly as in form login. The authenticated object goes into the SecurityContextHolder for this request only. Then the AuthorizationFilter checks the roles, as before.
Configuration
Only the security starter is needed; there is no session, so no spring-session-jdbc. Since form login is the default, override the SecurityFilterChain and choose basic authentication:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/users").hasAnyRole("USER") // authorization
.anyRequest().authenticated())
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) // no session
.csrf(csrf -> csrf.disable()) // no session cookie, so no CSRF risk
.httpBasic(Customizer.withDefaults()); // basic authentication
return http.build();
}
}STATELESS: no session is ever created; responses set no session cookie.- CSRF disabled: CSRF abuses credentials the browser sends automatically, such as a session cookie. Here the client adds the header itself on every request, so there is nothing to forge.
Results:
| Request | Response |
|---|---|
No Authorization header | 401, with WWW-Authenticate: Basic realm="Realm" |
| Wrong password | 401 |
| Correct credentials | 200 |
| Correct credentials, missing role | 403 |
The WWW-Authenticate header is what makes a browser show its built-in username/password popup.
Drawbacks
- Credentials travel with every request. Without HTTPS they can be intercepted and decoded.
- No way to revoke access short of changing the password: there is no session or token to invalidate.
- Poor fit for large applications:
- every request is larger, carrying the header
- every request repeats the work: decoding, hashing the incoming password and comparing
- every request loads the user from the database, adding latency
These drawbacks lead to token-based methods such as JWT (next parts).
Next: Part 5 explains JWT, a signed token that replaces sending the password with every request.
Check yourself
- Why is basic authentication stateless, and what must the client send with each request?
- Is
Basic dXNlcjpwYXNzsecure? What does it contain? - Give three reasons the credentials go in a header rather than the body.
- Which filter handles basic authentication, and what does it build?
- What is different about the flow compared with form login?
- Why is the session policy
STATELESS, and why can CSRF be disabled? - What does the server return when the header is missing, and why does a browser then show a login popup?
- Give three drawbacks of basic authentication.
Answers
- The server keeps no session; the client sends
Authorization: Basic <base64(username:password)>every time. - No: it is Base64 for
user:pass, which anyone can decode. Use HTTPS. - It is the HTTP standard (RFC 7617); bodies are more often logged than the Authorization header;
GEThas no body. BasicAuthenticationFilter; aUsernamePasswordAuthenticationToken.- Authentication and authorization happen in the same request, and no session is created; the context lives only for that request.
- The method keeps no state, so no session is needed; CSRF abuses automatically sent cookies, and there are none here.
401withWWW-Authenticate: Basic realm="Realm", which tells the browser to ask for credentials.- Credentials in every request (dangerous without HTTPS); no revocation except changing the password; overhead per request (bigger requests, decoding, hashing, database lookup).