Unleashing the Power of Spring Boot and Kotlin: Modernizing API Authorization Part II
Published: 2023-11-15 08:43:59

In our previous discussion, we explored the interns of Spring Security, gaining insights into the orchestration of filters and the crucial function of the `AuthenticationFilter` in user authentication. Now, we switch our attention to a practical skill: the generation of Json web tokens. This skill will enable us to tap into the advantages of token-based authentication, enabling a process of user authentication smooth when it requests the different API endpoints from our app.

This article is your guide to mastering three key aspects in three parts:

1. [**Login via API:**](https://dev.to/ruben_suet/unleashing-the-power-of-spring-boot-and-kotlin-modernizing-api-authorization-part-i-5cb5) We'll kick things off by tackling the initial API login.
2. [**Implementing a JWT for Persistence:**](https://dev.to/ruben_suet/unleashing-the-power-of-spring-boot-and-kotlin-modernizing-api-authorization-part-ii-2h8m) Learn how to keep your users authenticated via API using the shiny new JWT.
3. [**Connecting Database Users to Spring Security:**](https://dev.to/ruben_suet/unleashing-the-power-of-spring-boot-and-kotlin-modernizing-api-authorization-part-iii-2i8m) Dive into the realm of syncing your database users seamlessly with the Spring Security system.

You might have observed that even after a successful login, the user is restricted from performing any subsequent actions. This limitation arises because we've configured all endpoints to require authentication, and with each HTTP request, the process starts anew, lacking persistence for the user. To address this, [JSON web tokens(JWT)](https://jwt.io/introduction) can be a valuable solution, allowing us to maintain user sessions for continued operations.

Our approach involves issuing a token with a validity period of up to <u>10 days</u>. Beyond this duration, attempting further operations will result in a forbidden response, prompting the user to undergo the login process once again. To implement this, let's include the necessary dependencies in our `pom.xml`.

#### **`pom.xml`**
```java
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-jackson</artifactId>
  <version>0.12.3</version>
  <scope>runtime</scope>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-api</artifactId>
  <version>0.12.3</version>
</dependency>
<dependency>
  <groupId>io.jsonwebtoken</groupId>
  <artifactId>jjwt-impl</artifactId>
  <version>0.12.3</version>
  <scope>runtime</scope>
</dependency>
```

In contrast to the Spring Security package, the dependencies include a specified version. The version mentioned here corresponds to the one in use at the time of making this article. However, feel free to check for the [latest versions on the Maven Repository website](https://mvnrepository.com/search?q=jjwt) and opt for the most up-to-date version.

![Json Web Token diagram](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rytb8312vziwdee3th0f.png)

Esuring secure encryption, a JWT incorporates a signature that involves both the encryption algorithm and a secret key influencing the encoded token's outcome. The process involves three crucial steps:

- <u>JWT Creation upon User Login</u>: Generate a JWT when a user successfully logs in.

- <u>JWT Verification on Each Endpoint Request</u>: Validate the JWT on every endpoint request.

- <u>Token Validation with Correct Signature and Expiry Time</u>: Validate the token by ensuring it has the correct signature and has not expired.

#### Create a JWT when a user is logged in 
Let's initiate the process by creating a new file within the `security` package:

#### **`JwtGenerator.kt`**
```java
@Component
class JwtGenerator() {

    fun makeToken(username: String): String {
        val secretWord = "This is my  secret word for Dev.to"
        val keySecretWord = Keys.hmacShaKeyFor(secretWord.toByteArray())

        return Jwts.builder()
            .subject(username)
            .issuedAt(Date())
            .expiration(Date(System.currentTimeMillis() + 864000000)) // Token for 10 days
            .signWith(keySecretWord)
            .compact()
    }
}
```

We designate this class within the Spring Boot context using the `@Component` annotation since it lacks Business Logic. While an `@Service` alternative is plausible, the choice is flexible based on your preference. Although the use of an `object` instead of a class is plausible, a future refactor will introduce the injection of the `keySecretWord` variable rather than hard-coding it here.

The line `val keySecretWord = Keys.hmacShaKeyFor(secretWord.toByteArray())` is crucial for creating a `SecretKey` object. Here, the HMAC-SHA algorithm is employed, as detailed in the [documentation](https://javadoc.io/doc/io.jsonwebtoken/jjwt-api/0.11.2/io/jsonwebtoken/security/Keys.html#hmacShaKeyFor(byte[])). Should a different algorithm be more suitable for your needs, feel free to adjust it accordingly. The essential aspect is ensuring the presence of a SecretKey type.

It's important to note that the method `Keys.hmacShaKeyFor(secretWord.toByteArray())` requires a ByteArray type as a parameter, necessitating the transformation from a String.

Transitioning to the second part of the code, its responsibility lies in generating a new JWT:

```java
  return Jwts.builder()
            .subject(username)
            .issuedAt(Date())
            .expiration(Date(System.currentTimeMillis() + 864000000)) // Token for 10 days
            .signWith(keySecretWord)
            .compact()
```


- The `Jwts.builder()` starts the creation of a new JWT based on the information specified in the subsequent commands.
- The `subject` command defines the payload content; in this instance, we set the subject as the username, a parameter passed when calling the method.
- The `issuedAt` command generates a timestamp indicating when the token is generated.
- The `expiration` command determines when the generated token will expire. In our scenario, we set it to +10 days, but you can choose any time duration in milliseconds.
- The `signWith` command configures a signature using our encrypted secret word, enhancing the security of the JWT.
- The `compact` statement serves as the final directive to the builder, telling our builder that it's time to assemble the instance.

With this file, we can now integrate it into our login controller.

#### **`LoginController.kt`**
```java
@RestController
class LoginController(
val authenticationManager: AuthenticationManager,
val jwtGenerator: JwtGenerator) {

  @PostMapping("/login")
  fun login(@RequestBody loginRequest: LoginRequest): ResponseEntity<Void> {
    
     val authenticationRequest = UsernamePasswordAuthenticationToken.unauthenticated(				 
       loginRequest.username, 
       loginRequest.password
     )

     val authenticationResponse = authenticationManager.authenticate(authenticationRequest)
     
     val token = jwtGenerator.makeToken(loginRequest.username)

     return ResponseEntity.ok(LoginResponse(token))

  }

  data class LoginRequest(val username: String, val password: String)
  data class LoginResponse(val token: String)

}
```

1. The constructor injects the `JwtGenerator`.
2. A token is generated using `jwtGenerator.makeToken(loginRequest.username)` and stored in a variable.
3. If the token creation is successful, it returns the `LoginResponse` object.
4. The `LoginResponse` is a data class (`data class LoginResponse(val token: String)`) designed for returning data in JSON format. The response contains the generated token.

This is the outcome of the login process now:

![Thunderclient login response with JWT](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pgbnhvxujftgb1fw5cbk.png)

#### Verify JWT
To make use of the received JWT, we need a mechanism to inform Spring Security that the token is verified. If the verification is successful, the user can then access the requested resource. Let's implement a token filter within the `security` package:

#### **`JwtTokenFilter.kt`**
```java
@Component
class JwtTokenFilter() : OncePerRequestFilter() {

   
    override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
      val secretWord = "This is my  secret word for Dev.to"
      val keySecretWord = Keys.hmacShaKeyFor(secretWord.toByteArray())
      try {
        extractToken(request.getHeader("Authorization"))?.let { token ->
           Jwts.parser()
           .verifyWith(keySecretWord)
           .build()
           .parseSignedClaims(token)
           .payload
           .subject
           .let { user ->
             SecurityContextHolder.getContext().authentication =
                            UsernamePasswordAuthenticationToken(user, null, emptyList())
            }
          }
        } catch (e: SignatureException) {
            SecurityContextHolder.clearContext()
        }
        filterChain.doFilter(request, response)
    }

    private fun extractToken(authorizationHeaders: String?): String? {
        return authorizationHeaders?.takeIf { it.startsWith("Bearer ") }?.substring(7)
    }
```

This class inherits from `OncePerRequestFilter`, a subclass of `GenericFilterBean`. Understanding the difference between these two is crucial:

- `GenericFilterBean` is a special class where you must implement the `doFilterInternal` function. The logic you put inside will be encapsulated and run arbitrarily through Spring Security filters. Spring Security determines when this logic runs in the filter chain but can apply it multiple times.

- `OncePerRequestFilter` is similar to the above class but with a crucial distinction: the logic runs only once. It forces Spring Security to confirm that if the logic is validated, there is no need to revisit and re-check it.

The implementation convention for the `doFilter` method is as follows:
``` java
override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
```
Making sure that you include the parameters in this method is vital. At the end of our method, we execute `filterChain.doFilter(request, response)`. This statement ensures that the filters from the filter chain continue to run. Forgetting this line may break the flow and result in unexpected behavior.

Regarding `Keys.hmacShaKeyFor`, it is not necessary to provide an additional explanation. Just remember that it must exactly match the value used as a String previously. We will refactor it later to centralize this information, but for now, we'll proceed with a small proof of concept and make adjustments afterwards.

```java
 extractToken(request.getHeader("Authorization"))?.let { token ->
           Jwts.parser()
           .verifyWith(keySecretWord)
           .build()
           .parseSignedClaims(token)
           .payload
           .subject
           .let { user ->
             SecurityContextHolder.getContext().authentication =
                            UsernamePasswordAuthenticationToken(user, null, emptyList())
            }
          }
        } catch (e: SignatureException) {
            SecurityContextHolder.clearContext()
        }
        filterChain.doFilter(request, response)
    }
```

- We extract the token based on the helper method mentioned at the end of the file. This small helper method takes the first 7 characters from the authentication in case it exists. Typically, for JWT authentication, a common convention is to add the prefix "Bearer" and then the token. You can use any prefix, but it's crucial to consider the character count for extracting the prefix and white space. In ThunderClient for VSCode, there's an option for "Bearer" token type authentication, allowing easy inclusion of the prefix.

![Thunderclient with Bearer token](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/gpgpqnpajk0tcj4k22km.png)

If, for some reason, your API client lacks an authorization section, you can manually add it to the headers.

![API client with header Authorization](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhri8rioeilt28p2ggku.png)

- Once the token is extracted from "Bearer," we pass the value into a new context named token using the `let` command. The `?` before ensures that we have a value or return null otherwise, preventing the execution of the following block context if no value is present.
- The JWT parser is invoked to transform the encrypted JWT into a proper payload for reading. The same `secretKeyword` signature is used to apply the algorithm. It's crucial to use the same string; otherwise, the process will fail.
- The token is built, and its signature is verified with `parsedSignalClaims`, where it checks the algorithm and secret word inside the token against our own signature (HMAC-SHA algorithm and the secret word).
- We extract the payload (the content of the returned token, where the `username` was placed in the subject during token generation). The `subject` is extracted, holding the username.
- In the following critical process, if we have a username, a new context with `let` is invoked to pass the username as a value. `SecurityContextHolder` is invoked manually. Remember, the `SecurityContext` holds all the critical information. We're manually stating that this token corresponds to an authenticated user. We save the authentication as a new `UsernamePasswordAuthenticationToken` object, holding our username. The three parameters passed are:

  - **Principal**, which holds the username.
  - **Credentials**, holding the password. As we do not store a password in our JWT, it's set as null.
  - **Authorities**, representing the roles of the user. This is related to the authorization topic, which we do not address here. If needed, you could store the user's role in the payload and set it here. In our case, we pass an empty list because we treat all users on the website as the same type.

With this file, we can filter and check the provided token from the user, ensuring its validity based on our JWT signature, and set our user as authenticated. One part is missing: ensuring that our JWT filter is applied before the authentication filters like `BasicAuthenticationFilter` or `UsernameAndPasswordFilter`. Let's update our security filter chain method to instruct the framework to apply our custom filter before the authentication filters.

#### **`SecurityConfig.kt`**
```java
@Configuration
class SecurityConfig {
  // Other methods....
   @Bean
   fun securityFilterChain(http: HttpSecurity, jwtTokenFilter: JwtTokenFilter): SecurityFilterChain {
      http {
        csrf { disable() }
        addFilterBefore<UsernamePasswordAuthenticationFilter>(jwtTokenFilter)
        authorizeHttpRequests {
           authorize("/login", permitAll)
           authorize(anyRequest, authenticated)

        }
      }
        return http.build()
  }
}
```
The `JwtTokenFilter` is injected, and the method `addFilterBefore` is specified. In our language, `addFilterBefore<UsernamePasswordAuthenticationFilter>(jwtTokenFilter)` indicates: Before the `UserNamePasswordAuthenticationFilter`, please apply the custom `jwtTokenFilter`. Now our filter placement is not arbitrary; we have deliberately decided where to position it. This ensures that an authenticated user is created before all the necessary checks are applied, facilitating the framework's operation and allowing us access to the endpoints.


![Thunderclient success login with JWT step1](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0dfrjh2u53g1njsimwqo.png)


![Thunderclient failed authorization without JWT step2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yr1f8ade14hispephsd8.png)


![Thunderclient successful authorization with JWT provided step2](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/po4ep0w3zwm7k75mah7m.png)


#### Validate JWT
This is already looking good, but currently, only the signature is being checked. What if we also want to verify the expiration date? Same as before, the validation logic will be implemented in another file in the `security` package, where we set the same secret word and algorithm to transform it into a SecretKey and then validate.

#### **`JwtTokenValidator.kt`**
```java
class JwtTokenValidator {
    fun validateToken(token: String) {
      val secretWord = "This is my  secret word for Dev.to"
      val keySecretWord = Keys.hmacShaKeyFor(secretWord.toByteArray())
        try {
          val jws: Jws<Claims> = Jwts.parser()
            .verifyWith(keySecretWord)  
            .build()
            .parseSignedClaims(token)      

          if (jws.payload.expiration.before(Date())) {
              throw RuntimeException("Token is expired")
          }

        } catch (e: SignatureException) {
            throw RuntimeException("Invalid Token Signature", e)
        } catch (e: Exception) {
            throw RuntimeException("Token Validation Failed", e)
        }
    }
}
```
In this case, I've chosen not to annotate it with `@Component` since I will only use it in the `JwtTokenFilter`, and I already have all the necessary information to make it work. Therefore, the Spring context is not needed to manage this file. Focusing on this snippet:
```java
 val jws: Jws<Claims> = Jwts.parser()
            .verifyWith(keySecretWord)  
            .build()
            .parseSignedClaims(token)      

          if (jws.payload.expiration.before(Date())) {
              throw RuntimeException("Token is expired")
          }
```
As in the filter, it is parsed from what is received as a token into an object that can be processed. In this case, we check again the signature with verifyWith and `parseSignedClaims`. In reality, this step could be skipped since we are already checking it before in the filter, but I think it does not harm to check it out twice, and it's not computationally expensive if checked twice.

The interesting part is when we are extracting the expiration, and it is compared with a `Date()` that will return your current system time. If it's expired, then an exception is thrown. We can test this validation if we change the expiring date from our JWT when it's generated.

Let's add the validation file to our filter:
#### **`JwtTokenFilter.kt`**
```java
class JwtTokenFilter() : OncePerRequestFilter() {

  private val tokenValidator = JwtTokenValidator()

 override fun doFilterInternal(
        request: HttpServletRequest,
        response: HttpServletResponse,
        filterChain: FilterChain
    ) {
        // Secret words variables...
        try {          
          extractToken(request.getHeader("Authorization"))?.let { token ->
                tokenValidator.validateToken(token)
                Jwts.parser()
                    .verifyWith(keySecretWord)
                // rest of the method...
        }
    }
}
```
Notice how I added `tokenValidator` as a property in the class, and then when the token is checked via `let`, before proceeding, we apply our `validateToken` by sending the token.

You can change your JWT generator milliseconds option to only last for 10 seconds; try to log in and check an endpoint constantly with the token for the following 10 seconds, and you will notice that eventually, it will return a `403 forbidden`.

#### Refactoring the secret word

It is not ideal to have the same secret word defined and transformed into `SecretKey` up to three times. Let's add the word in our application.properties.

#### **`application.properties`**
```
jwt.secret-word=This is my  secret word for Dev.to
```
In our SecurityConfig file, we can retrieve it as a `@Value` and create a `@Bean` for it.

#### **`SecurityConfig.kt`**
```java
@Configuration
class SecurityConfig {
    @Value("\${jwt.secret-word}")
    val secretWord: String? = null
 
    @Bean
    fun jwtSecretKeyword(): SecretKey = Keys.hmacShaKeyFor(secretWord?.toByteArray())

  // The other methods that we worked through this tutorial
}
```
`@Value` injects into `secretWord` the value of our `application.properties`. Since this is Kotlin, the `$` has a special meaning, and we need to escape it to make it work for the value. Now it's only a matter of injecting it into the constructor of the generator and the filter:

#### **`JwtGenerator.kt`**
```java
@Component
class JwtGenerator(private val jwtSecretKeyword: SecretKey) {
  fun makeToken(username: String): String {
     // I removed the secretWord 
     return Jwts.builder()
     // Methods from the builder
      .signWith(jwtSecretKeyword) // injected secret word
     // Rest of the method
  }
}
```
#### **`JwtTokenFilter.kt`**
```java
@Component
class JwtTokenFilter(private val jwtSecretKeyword: SecretKey) : OncePerRequestFilter() {

    private val tokenValidator = JwtTokenValidator(jwtSecretKeyword) // I am sending the secret word from the constructor to the validator

  // change .verifyWith(keySecretWord) with .verifyWith(jwtSecretKeyword)
```

In our validator too:
#### **`JwtTokenValidator.kt`**
```java
class JwtTokenValidator(private val jwtSecretKeyword: SecretKey) {
   // change .verifyWith(keySecretWord) with .verifyWith(jwtSecretKeyword)

}

```
This is why I wanted to go with a class since the beginning with this file. If we didn't have anything in the constructor, we could have an `object` instead of a class, which would have ensured that this would be a singleton in our app. However, since this will only be used in the `TokenFilter` and it will be already a bean singleton, our validator should be safe as well.

One last refactoring to apply: Our Controller is managing the login system from the previous section. We could move the authentication process to a service, and then our controller would only be able to receive the HTTP call and send the info to the different services that will manage the logic.

Let's create an `AuthenticatorService` in our `security` package:
#### **`AuthenticatorService.kt`**
```java
@Service
class AuthenticatorService(val authenticationManager: AuthenticationManager) {
    fun authenticate(username: String, password: String) {
        val authenticationRequest =
            UsernamePasswordAuthenticationToken.unauthenticated(
                username, password
            )

        authenticationManager.authenticate(authenticationRequest)
    }
}

```
And our `LoginController` will look like this:
#### **`LoginController.kt`**
```java
@RestController
class LoginController(
    val jwtGenerator: JwtGenerator,
    val authenticatorService: AuthenticatorService,
) {

    @PostMapping("/login")
    fun login(@RequestBody loginRequest: LoginRequest): ResponseEntity<LoginResponse> {
        authenticatorService.authenticate(loginRequest.username, loginRequest.password)

        val token = jwtGenerator.makeToken(loginRequest.username)

        return ResponseEntity.ok(LoginResponse(token))
    }
}
```
In this section, we learned:

- Creating our own JWT when a user logs in
- Know about how to add custom filters in the `FilterChain` and verify from there our JWT
- Place our custom filter in a specific order
- Validate our token to make sure that the signature and expiry date are still valid