Spring Security JWT Authentication
JSON Web Tokens (JWT) are commonly used for user authentication. It is a token generated by the backend and given to the frontend to be used in the subsequent requests as the session identifier.
JWT tokens are consist of 3 parts separated by dots, as header
.payload
.signature
. Header contains the the token type and the algorithm used to sign, such as HMAC SHA256 or RSA. The payload contains the claims which is the data to be stored in the token, and you can think like key value pairs.
{
"timestamp": "1234567890",
"name": "Frodo Baggins",
"group": "Fellowship",
"active: true
}
The signature part contains the signature created by the algorithm that is using the header, payload, and a secret key. So the signature is used to verify that the message is not changed on the way by any intruder. So we should keep the secret key private. Read more details on JWT Details: jwt.io. An example token looks like as follows:
After login operation, backend creates such a token and sends back to frontend. In the following requests frontend should send this key for example in the header Authorization
in order to recognise the owner of the session. Backend application should verify this token at each API call as follows:
- Check if the JWT token exists in the Authorization header
- Validate the token
- Otherwise reject with permission denied (403)
- If it is a login call, check the credentials, generate a token and send it back.
Initiate the Spring Project
We initiate our project from Spring Initializr with the web
, jpa
and security
dependincies. We need to make sure that we have the security dependency:
After that we can run a database for example on docker.
docker run -e POSTGRES_PASSWORD=root -d -p 5432:5432 -v db-data:/var/lib/postgresql/data postgres
And provide the connection details in application.yml
User Registration
Spring provides UserDetails
class that can be used for authentication so we need to extend this class and generate our ApplicationUser
entity. We can add desired fields for a user and we also need to override the parent's methods.
In order to interact with the database we can use JPA's JpaRepository
. It has all the built in save, update, find, delete methods. We only need to add findByUsername
method that will be need later.
@Repository
public interface ApplicationUserRepository extends JpaRepository <ApplicationUser, Long> {
ApplicationUser findByUsername(String username);
}
Spring Security also provides built-in UserDetailsService
. We only need to extend it override the loadUserByUsername
method to configure how the user will. be retrieved. So it gives us the prototype, but implementation is flexible.
We have our service and repository, now we can create the controller to expose the registration API to our clients.
When we receive the user details to be saved, we are encoding the user password with Bcrypt encoder. We can create BCryptPasswordEncoder
bean as follows:
Now we expose the /api/users/sign-up/
endpoint to our users. We can send user details to this endpoint and it will be saved to database.
User Authentication and Authorization
In this step we will secure our endpoints and configure the spring via security filters to check authorization (if the user is eligible to access the resource) , and also handle the authentication while logging in. First of all let's keep our constants in a class:
And then we will apply following steps:
- Extend the
WebSecurityConfigurerAdapter
to customise the security configuration.
We are configuring our endpoints to be private, except the SIGN_UP_URL
and actuator
which can be used for health checks. So to be able to access rest of the endpoints a user need to have an Authorization
header with issued JWT Token. We are adding the Authentication filter and the Authorization filter in the security filter chain. And we are setting our UserDetailsService
to be used for checking credentials. Finally we configure the Cors
configuration.
- Create authentication filter that will issue JWTs to users logging in
So we are extending UsernamePasswordAuthenticationFilter
and implementing its methods:
attemptAuthentication: checks the user credentials
successfulAuthentication: issues the JWT token. Note that it is adding user id and username claims to the token. We can also add any custom data to be stored as a claim inside the token.
- Create authorization filter to validate requests containing JWTs
For this one we are extending the BasicAuthenticationFilter
and implementing its method doFilterInternal
. This method is checking if there is a HEADER_STRING
, which is Authorization
in the request. If there is no header or the token is null, it calls. chain.doFilter(req, res);
to continue the filter chain and returns. If there is a token in the header our getAuthentication
method tries to verify the token with using the SECRET
key and parses the token.
We can retrieve our claims in here. For example with retrieving userId from the token we can find the user details from the database and set as principal. Spring keeps the principal object in the Security context as the current user. Later on in our services if we need to get the user who makes the call we can retrieve it from Security context as follows:
ApplicationUser principal = (ApplicationUser) SecurityContextHolder.getContext().getAuthentication().getPrincipal();
Demo
As the spring security automatically exposes the endpoint /login
we can register a user and try to login.
- Register User
- Login. After Successful login we receive JWT in the response header
- If we try to access a private endpoint without sending JWT, security filters will complain and do not allow as the request is not authorized
- If we access a private endpoint with the issued JWT, we will be allowed as we are authorized, and our JWT token proves our session.
So we can store the JWT issued after the login inside the cookies or local storage at our frontend application and send for each request.
Bare in mind that there are some drawback of using JWT as Auth Token. As we are not storing tokens in the database, in other words we do not store the user sessions but only verify if the received token is valid or not, we can not invalidate the tokens. So if we ban a user, or delete user its token will be still valid. If the user takes the token manually from the local storage or cache content and repeat the call with supplying the token it will be authorized. So we have to wait until token timeouts.
If we store the token in database and control it while checking auth. then it brings extra complexity of making database call per request. And also as JWT tokens are long it takes relatively big space in the database, so it is also not very efficient to store it. Moreover verify the token consumes time as well for each request. So if we use JWT authentication for internal service communication it would be efficient to verify token without making db calls and storing the sessions, but we may face the listed problems for using REST calls open to your clients. Refer to Link for more details about the drawbacks.