How to Make a Stateless (Session-less) Authentication With Spring
In this article, I want to focus on how to deal with automatic re-authentication of each HTTP and HTTPS requests. Read on for more info.
Join the DZone community and get the full member experience.
Join For FreeIf we think about the meaning of authentication, it seems that it is all about a client identifying itself to the server. After client identification is done, the server can remember the client each time the request comes from the client. There are two common approaches to authentication mechanisms: one of them is called "Session Cookie Based" and the other one is "Token Based".
In this article, I want to focus on how to deal with automatic re-authentication of each HTTP and HTTPS requests.
What Does "Session Cookie Based Authentication" Mean?
In today's world, especially for corporate businesses, the most common usage of authentication is the session-based approach. In the session-based approcah, a session id—which is a kind of server generated token—is generated and stored in a cookie within the JSESSIONID paramter. This means that the server stores the session key in itself so when the server reboots or requests are redirected to another server by load balancers, your "state" of session key becomes useless.
What Does "Stateless Authentication" Mean?
Whenever you are talking about REST API's , API keys are mentioned too. Basically, they involve sending custom tokens or custom keys within the HTTP Request header. There are several approaches such as OAUTH1, OAUTH2, Basic Authentication, etc. for implementing stateless authentication and today we will be focus on "Server Signed Token" approach that may be life-saving for your implementations.
Server Signed Token Approach
First of all, it is easy to imagine that, in this approach; if server gets a request from the client, the client should send a token and the server should check and sign it before giving any response to the client.
With "Server Signed Tokens" a user identification data is shared with the server for authentication and when the server authenticates the user, it gives a hash key which is called a "token" to the client. After this step, the client can use this token to ask for any request to the server so the server should sign and approve that the token is valid for request.
Let's get into the details of the implementation of this approach.
The first step is authentication. In the authentication step, the client sends its user-specific identification data to the server and if this data is correct, the server response is a token to the client with HTTP 200 OK. If the data is incorrect, the server response is an HTTP 403 Forbidden.
After this approval is done, the client can send another requests without giving any user-specific identification data to the server again again. The client only uses this token for further requests.
The Recipe for Implementing Token Approach With Spring
To understand all of the steps easily, it is good to have a scenario. For example, we want to make a login page with the inputs of username and password. And, after the user is logged in successfully, we want to show the user profile page with user information in it.
In Spring, we have WebSecurityConfigurerAdapters which help to implement the security configurations of our spring project easily. In these adapters, there is a "configure" method for HTTP securities.
In this configure method, we should add two filters which will be used in login step and the authentication step.
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable();
http.exceptionHandling()
.and()
.anonymous()
.and()
.servletApi()
.and()
.headers().cacheControl()
.and()
.authorizeRequests().antMatchers(HttpMethod.OPTIONS,"/api/auth/login").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new LoginFilter(new AntPathRequestMatcher("/api/auth/login")), UsernamePasswordAuthenticationFilter.class)
.addFilterBefore(new AuthFilter(), UsernamePasswordAuthenticationFilter.class);
}
Full Verion Of The Java Class Can Be Found Here
As you see in the last two lines, we have a "Login Filter" which will be called with any request path equals to "/api/auth/login". Also, for any other requests the "Auth Filter" will be called before each request is processed.
Spring provides an "AbstractAuthenticationProcessingFilter" which is used for authentication.
Because of this helpfull abstract class, we extend our Login Filter from the AbstractAuthenticationProcessingFilter. This filter has "attemptAuthentication", "successfulAuthentication" and "unsuccessfulAuthentication" methods to override.
In attemptAuthentication method, the aim is to create an Authentication object (org.springframework.security.core.Authentication) that stores the authorities, credentials, details, principal object, and authenticationResult.
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException, IOException, ServletException {
UserAuthentication auth = (UserAuthentication) tokenAuthenticationService.getAuthenticationForLogin(request,
response);
if (!auth.isAuthenticated()) {
throw new UserAuthenticationException("Auth to FTAPI is Failed.", auth);
}
return auth;
}
If the attemptAuthentication method throws a UserAuthenticationException the unsuccessfullAuthentication method is called, otherwise the successfullAuthentication method will be called.
So, the aim of this method is to just check whether the username and password is correct or not, and then set the isAuthenticated parameter to true and false. If the parameter is false, it throws an exception to process the success and fail operations.
@Override
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response,
javax.servlet.FilterChain chain, Authentication authResult) throws IOException, ServletException {
try {
UserAuthentication authResultObject = (UserAuthentication) authResult;
tokenAuthenticationService.addAuthentication(response, authResultObject);
// Add the authentication to the Security context
SecurityContextHolder.getContext().setAuthentication(authResult);
} catch (Exception ex) {
ex.printStackTrace();
}
}
As it is easy to understand from the implementation, the token is created in the addAuthentication method and stored in the security context.
A common question is: where do you store the tokens for both client and server. It depends on your requirements. For example, you can create the token and store it in a cache server with a token, user hashmap and when the user sends a request with that token, you can get the token details from this cache server or you can store the token you generated in a database, etc.
When the server creates the token, this token should be told to the client in the HTTP response. The common best practice of that is to respond to this token in the header of the HTTP reponse with a parameter name which is called "X-AUTH-TOKEN". This step is very important because the client will always come with this X-AUTH-TOKEN parameter which contains a token for each HTTP request.
After the token handshake is done, when the client makes a request to the server, Auth Filter will be called. The working style of the auth filter is very easy because the only job it needs to do is get the X-AUTH-TOKEN value from the request header and get the user object from the cache server. If the user object already exits, that means there is a user that has a valid and not dead token. If the user object does not, all the filter needs to do is changing the response HTTP status code to 403 which means unforbidden.
Opinions expressed by DZone contributors are their own.
Comments