<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Authorization Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/authorization/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:51 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	

<image>
	<url>https://blog.codersee.com/wp-content/uploads/2025/04/cropped-codersee_logo_circle_2-32x32.png</url>
	<title>Authorization Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Secure REST API with Ktor &#8211; Role-based access control (RBAC)</title>
		<link>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/</link>
					<comments>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 19 Dec 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[RBAC]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008380</guid>

					<description><![CDATA[<p>In this, 3rd article in a series, I will show you how to add role-based access control (RBAC / role-based security) to our Ktor project.</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/">Secure REST API with Ktor &#8211; Role-based access control (RBAC)</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to the 3rd article in our secure REST API with Ktor series, in which I will show you how to set up <strong>role-based access control (RBAC)</strong>, also known as role-based security. </p>



<p>If you haven&#8217;t seen my previous articles, then I highly encourage you to check them out: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/" target="_blank" rel="noreferrer noopener">Secure REST API with Ktor and JWT Access Tokens</a></li>



<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">Secure Ktor app with JWT refresh tokens. Refresh token flow</a>.</li>
</ul>



<p>Moreover, in this tutorial, we will build functionality based on the refresh token article, so I highly encourage you to fetch the code from <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">this repo</a> now. <strong>But don&#8217;t worry</strong>, if you are interested only in the RBAC part, then please navigate to the &#8220;<em>Custom Ktor Authorization Plugin</em>&#8221; paragraph.</p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FgvhXfzl34pk%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-exactly-will-we-do-today">What Exactly Will We Do Today? </h2>



<p>At the end of this tutorial, you will know precisely how to secure your Ktor REST API with role-based access control (RBAC). </p>



<p>But to be more specific, we will work with two endpoints: </p>



<figure class="wp-block-image size-large"><img fetchpriority="high" decoding="async" width="1024" height="648" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-1024x648.png" alt="Image shows two endpoint - get all users and get user by ID which will be secured with RBAC" class="wp-image-9008393" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-1024x648.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-300x190.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement-768x486.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_what_will_we_implement.png 1252w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The <code>GET /api/user/{id}</code> will be accessible for users with either <code>ADMIN</code> or <code>USER</code> roles. The second one, <code>GET /api/user</code>, will be accessible only for <code>ADMIN</code> users. </p>



<p>And how we will achieve that? </p>



<p>Well, we will implement a <strong>custom Ktor authorization plugin</strong>, which will be triggered on the <strong>AuthenticationChecked hook</strong>. </p>



<p>And I&#8217;ll show you how to do it the easy way, without unnecessary logic 🙂 </p>



<h2 class="wp-block-heading" id="h-what-is-role-based-access-control-rbac">What Is Role-Based Access Control (RBAC)? </h2>



<p>Again, before we learn how to implement role-based access control (RBAC) in Ktor, let&#8217;s learn a bit about it. </p>



<p><strong>Role-based access control</strong> (RBAC) or role-based security is nothing else than a way of restricting system access to authorized users. </p>



<p>To put it simply, we define a set of <strong>roles</strong> in our system, which reflects the needs of given permissions from our organization. An example of such a role can be a <em>user, manager, admin</em>, and many many more. And based on this role we allow, or deny access for a particular, authenticated user. </p>



<p>When dealing with JWT tokens in a REST API, such information can be first stored inside the token, so that later, we can easily read a claim and check whether a request should be served, or not. </p>



<h2 class="wp-block-heading" id="h-update-user-class-and-repo">Update User Class and Repo</h2>



<p>With all of that said, let&#8217;s finally start the practice part. </p>



<p>As the first step, let&#8217;s navigate to the <code>User</code> class and insert a new field- <code>role</code>:  </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.UUID

data class User(
  val id: UUID,
  val username: String,
  val password: String,
  val role: String
)</pre>



<p>For the sake of simplicity, let&#8217;s leave it as a String value, but depending on your needs, you may want to consider using an <strong>enum</strong>, or even a <strong>sealed class</strong> instead.</p>



<p>At this point, our code won&#8217;t compile, so let&#8217;s navigate to the <code>UserRoute.kt</code> and update the <code>.toModel()</code> extension function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password,
    role = "USER"
  )</pre>



<p>As we can see, by default, all created users will have the <code>USER</code> role assigned. </p>



<p>Lastly, let&#8217;s navigate to the <code>UserRepository</code> and add some <code>ADMIN</code> user:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class UserRepository {

  private val users = mutableListOf(
    User(UUID.randomUUID(), "admin", "password", "ADMIN")
  )

  // the rest of the class
}</pre>



<h2 class="wp-block-heading" id="h-add-role-claim-to-jwt-token">Add Role Claim to JWT Token</h2>



<p>As the next step, let&#8217;s open up the <code>JwtService</code> and update these 3 functions: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun createAccessToken(username: String, role: String): String =
  createJwtToken(username, role, 3_600_000)

fun createRefreshToken(username: String, role: String): String =
  createJwtToken(username, role, 86_400_000)

private fun createJwtToken(
  username: String,
  role: String, 
  expireIn: Int,
): String =
  JWT.create()
    .withAudience(audience)
    .withIssuer(issuer)
    .withClaim("username", username)
    .withClaim("role", role)
    .withExpiresAt(Date(System.currentTimeMillis() + expireIn))
    .sign(Algorithm.HMAC256(secret))</pre>



<p>As I mentioned in the &#8220;theory&#8221; part- when dealing with JWT tokens, we can put information about the user&#8217;s role inside the token. </p>



<p>And that&#8217;s exactly what is happening here. From now on, every access and refresh token will contain an additional claim- the <code>role</code>&#8211; which will be populated with a role assigned to a user. </p>



<h2 class="wp-block-heading" id="h-update-userservice">Update UserService </h2>



<p>With that done, we must navigate to the <code>UserService</code> and update <code>authenticate</code> and <code>refreshToken</code> functions: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun authenticate(loginRequest: LoginRequest): AuthResponse? {
  val username = loginRequest.username
  val foundUser: User? = userRepository.findByUsername(username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
    val accessToken = jwtService.createAccessToken(username, foundUser.role)
    val refreshToken = jwtService.createRefreshToken(username, foundUser.role)

    refreshTokenRepository.save(refreshToken, username)

    AuthResponse(
      accessToken = accessToken,
      refreshToken = refreshToken,
    )
  } else
    null
}

fun refreshToken(token: String): String? {
  val decodedRefreshToken = verifyRefreshToken(token)
  val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

  return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
    val foundUser: User? = userRepository.findByUsername(persistedUsername)
    val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

    if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
      jwtService.createAccessToken(persistedUsername, foundUser.role)
    else
      null
  } else
    null
}</pre>



<p>From now on we must pass the user role to the <code>JwtService</code> when creating new tokens and that&#8217;s exactly what we updated here. </p>



<h2 class="wp-block-heading" id="h-custom-ktor-authorization-plugin">Custom Ktor Authorization Plugin</h2>



<h3 class="wp-block-heading" id="h-what-are-ktor-plugins">What Are Ktor Plugins? </h3>



<p>Before we implement our custom one, let&#8217;s understand <strong>what exactly Ktor plugins are</strong>.</p>



<p>Basically, plugins in Ktor are a way to implement a common functionality that is out of the scope of the application logic. But what do we mean by that? Well, things like serialization, deserialization, compression, encoding, etc.</p>



<p>And when we take a look at the following diagram:</p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="612" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-1024x612.png" alt="" class="wp-image-9008395" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-1024x612.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-300x179.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram-768x459.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_rbac_plugins_diagram.png 1468w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>We will clearly see that plugins are everything that sits between request, application handler, and response. And yes, routing is a plugin too. </p>



<p>At this point, we can see that a custom plugin will be a great wait to achieve RBAC in our Ktor application. Moreover, <strong>we will use a new simplified API for creating custom plugins introduced in Ktor v2.0.0</strong>.</p>



<h3 class="wp-block-heading" id="h-implement-ktor-rbac-plugin">Implement Ktor RBAC Plugin</h3>



<p>With all of that said, let&#8217;s navigate to the <code>plugins</code> package and create the <code>RoleBasedAuthorization.kt</code> and write the following: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*
import io.ktor.server.response.*

class PluginConfiguration {
  var roles: Set&lt;String> = emptySet()
}

val RoleBasedAuthorizationPlugin = createRouteScopedPlugin(
  name = "RbacPlugin",
  createConfiguration = ::PluginConfiguration
) {
  val roles = pluginConfig.roles

  pluginConfig.apply {

    on(AuthenticationChecked) { call ->
      val tokenRole = getRoleFromToken(call)

      val authorized = roles.contains(tokenRole)

      if (!authorized) {
        println("User does not have any of the following roles: $roles")
        call.respond(HttpStatusCode.Forbidden)
      }
    }
  }
}

private fun getRoleFromToken(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("role")
    ?.asString()</pre>



<p>Firstly, we create the <code>PluginConfiguration</code> class with a <code>roles</code> field. This way, we will be able to configure later, which roles should be allowed for the given endpoint and which should be forbidden. </p>



<p>Later, we introduce our new, custom Ktor plugin using the <code>createRouteScopedPlugin</code> function. We must do that if we want to introduce a plugin that can be installed <strong>for a specific route</strong>. </p>



<p>Inside this function, we refer to  <code>pluginConfig</code> and configure what exactly our custom authorization plugin must do. Firstly, we set the <code>on</code> handler that accepts a <code>Hook</code> as a parameter- in our case the <code>AuthenticationChecked</code> is a hook. As per the <a href="https://ktor.io/docs/custom-plugins.html#other" target="_blank" rel="noreferrer noopener">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>AuthenticationChecked is executed after authentication credentials are checked. </cite></blockquote>



<p>Which simply means, that <strong>our plugin will be invoked after we authenticate the user successfully.</strong> With the old API, we would have to use the after <code>Authentication.ChallengePhase</code>.</p>



<p>Following, we <strong>extract the user role from the JWT <code>role</code> claim</strong> and if it is inside the Set with allowed roles, then we do nothing. Otherwise, we return <strong>403 Forbidden</strong>. </p>



<h2 class="wp-block-heading" id="h-add-roleutil">Add RoleUtil</h2>



<p>With that done, let&#8217;s navigate to the <code>util</code> package and introduce the <code>authorized</code> function inside the <code>RouteUtil.kt</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.RoleBasedAuthorizationPlugin
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Route.authorized(
  vararg hasAnyRole: String,
  build: Route.() -> Unit
) {
  install(RoleBasedAuthorizationPlugin) { roles = hasAnyRole.toSet() }
  build()
}</pre>



<p>To put it simply, we will use this function whenever we would like to <strong>authorize </strong>our request using role-based access control. </p>



<p>As a result, it will <strong>register our authorization plugin for a particular route.</strong></p>



<h2 class="wp-block-heading" id="h-update-user-routes">Update User Routes</h2>



<p>And with that said, let&#8217;s see it in action:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">authenticate {
  authorized("ADMIN") {

    get {
      val users = userService.findAll()

      call.respond(
        message = users.map(User::toResponse)
      )
    }

  }
}

authenticate("another-auth") {
  authorized("ADMIN", "USER") {

    get("/{id}") {
      val id: String = call.parameters["id"]
        ?: return@get call.respond(HttpStatusCode.BadRequest)

      val foundUser = userService.findById(id)
        ?: return@get call.respond(HttpStatusCode.NotFound)

      if (foundUser.username != extractPrincipalUsername(call))
        return@get call.respond(HttpStatusCode.NotFound)

      call.respond(
        message = foundUser.toResponse()
      )
    }
  }</pre>



<p>As we can see, from now on we can simply use the <code>authorized</code> whenever we want to limit endpoint accessibility to particular roles. </p>



<p>Of course, we must remember that this can be done only when the user is <strong>authenticated</strong>, because otherwise, we won&#8217;t be able to read the <code>role</code> claim. </p>



<h2 class="wp-block-heading" id="h-ktor-rbac-summary">Ktor RBAC summary</h2>



<p>And that&#8217;s all for this article on how to implement role-based access control (RBAC) in Ktor. </p>



<p>I hope this article (and a series) helped you to learn how easily we can set up different things related to security in Ktor. If you haven&#8217;t seen previous materials yet, then you can do that right here: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a></li>



<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a></li>
</ul>



<p>As always, you can find a source code in <a href="https://github.com/codersee-blog/kotlin-ktor-custom-authorization-plugin-rbac-jwt/tree/main" target="_blank" rel="noreferrer noopener">this GitHub repository</a> and <strong>let me know your thoughts</strong> in the comments section below 🙂</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/">Secure REST API with Ktor &#8211; Role-based access control (RBAC)</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
		<item>
		<title>Secure Ktor app with JWT refresh tokens. Refresh token flow.</title>
		<link>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/</link>
					<comments>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 28 Nov 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Resfresh Tokens]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008372</guid>

					<description><![CDATA[<p>In the second article of my secure Ktor series, I will show you how to implement a JWT refresh token flow functionality.</p>
<p>The post <a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>In the second article of my secure <strong>Ktor </strong>series, I will show you how to implement a <strong>JWT refresh token flow</strong> functionality.</p>



<p>To not duplicate ourselves, we will use the project implemented in the previous blog post, in which we learned how to implement a secure <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/" target="_blank" rel="noreferrer noopener">REST API with Ktor and authenticate with JWT access tokens</a>. So if you haven&#8217;t read that article yet, I highly encourage you to do it now. Nevertheless, if you are interested more in the <strong>refresh token part</strong>, you can find a link to the GitHub repository and simply clone the project. </p>



<p>Without any further ado, let&#8217;s get to work 🙂 </p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2F97_u55vGQz0%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-a-tiny-bit-of-theory">A Tiny Bit Of Theory</h2>



<p>If you&#8217;re following me for a longer period, you know that I focus on the practical side of different problems. </p>



<p>Nevertheless, I owe you a bit of explanation before we learn how to secure our Ktor app with JWT refresh token flow. So, in the following paragraphs, I will shortly describe: </p>



<ul class="wp-block-list">
<li>the difference between access tokens and refresh tokens,</li>



<li>what is a refresh token flow, </li>



<li>and advantages of such a flow. </li>
</ul>



<p>Of course, if you feel that&#8217;s not necessary in your case, then feel free to skip to the <em>Implement RefreshTokenRepository</em> section 😉 </p>



<h3 class="wp-block-heading" id="h-access-tokens-vs-refresh-tokens">Access Tokens vs Refresh Tokens</h3>



<p>Let&#8217;s start everything by distinguishing these two. </p>



<p>First of all, they both play distinct roles in token-based authentication systems. </p>



<p>An <strong>access token</strong> is a <strong>short-lived</strong> credential that is issued to a client after a successful authentication. Its purpose is to grant the client permission to access protected resources on behalf of the user. And by the client, we mean here a web app, a mobile app, or any other integration working with our REST API. </p>



<p>They are designed to have a limited lifespan to enhance security, reducing the risk associated with compromised tokens. </p>



<p><strong>Refresh tokens</strong>, on the other hand, are l<strong>ong-lived</strong> credentials that are used to obtain new access tokens without requiring the user to re-authenticate. </p>



<p>While access tokens are meant for short-term authorization, refresh tokens provide a mechanism for obtaining fresh access tokens and extending the user&#8217;s session securely. </p>



<h3 class="wp-block-heading" id="h-refresh-token-flow">Refresh Token Flow</h3>



<p>To put it simply, when a client authenticates successfully, it receives two tokens: an access token, and a refresh token. </p>



<p>The access token will be used to query our API. If it is compromised, the bad actor will have a limited time to perform unwanted actions on behalf of the user (thanks to its short-lived nature). </p>



<p>The refresh token, with a longer expiration time, will allow the client to get a new access token whenever it expires. This way, the user does not need to specify credentials (like username, and password for instance) every time an access token expires. </p>



<p>With this flow, we can strike a balance between usability and security. We improve user experience and maintain protection against unauthorized access.</p>



<h3 class="wp-block-heading" id="h-pros-vs-cons">Pros vs Cons </h3>



<p>As with every solution, refresh token flow has both pros and cons. </p>



<p>When it comes to the advantages, we already covered the first two- the shorter lifespan of access tokens <strong>limits the potential impact of a token compromise</strong>. Additionally, a long-living refresh token means a <strong>better user experience</strong>&#8211; a longer time between asking the user for his credentials. Lastly, this flow gives us <strong>more granular control</strong> over token management, for example, we can revoke refresh tokens selectively. </p>



<p>When it comes to the disadvantages, we could call them <strong>additional complexity</strong>. Not only we must carefully implement on the backend to prevent any vulnerabilities, but also, the storage and transmission of refresh tokens need to be handled securely to prevent unauthorized access.</p>



<p>Lastly, should we always use refresh token flow in our applications? </p>



<p>The answer is- as always- <strong>it depends</strong>. When dealing with security for our app we should spend some time investigating in depth all options on the table. Such an analysis would be way too much for the article about implementing a Ktor JWT refresh token flow 😉 </p>



<h2 class="wp-block-heading" id="h-implement-refreshtokenrepository">Implement RefreshTokenRepository</h2>



<p>With all of that said, we can finally start the practice part. </p>



<p>As the first step, let&#8217;s navigate to the <code>repository</code> package in our Ktor project and add the <code>RefreshTokenRepository</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">class RefreshTokenRepository {

  private val tokens = mutableMapOf&lt;String, String>()

  fun findUsernameByToken(token: String): String? =
    tokens[token]

  fun save(token: String, username: String) {
    tokens[token] = username
  }

}</pre>



<p>Again, in a real-life scenario, this would be the class responsible for communicating to the data source, like a relational database, MongoDB, or any other source used in the project. To keep our tutorial simple and focused on the Ktor/JWT combination, we will use a mutable map storing a combination of tokens with usernames in memory. </p>



<p>As we can see, this class exposes two functions. One is for persisting the data in our map, and the second one is to retrieve usernames based on the JWT refresh token value. </p>



<h2 class="wp-block-heading" id="h-update-jwtservice">Update JwtService </h2>



<p>Nextly, let&#8217;s open up the <code>JwtService</code> class and update it a bit: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import com.codersee.repository.UserRepository
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userRepository: UserRepository,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  fun createAccessToken(username: String): String =
    createJwtToken(username, 3_600_000)

  fun createRefreshToken(username: String): String =
    createJwtToken(username, 86_400_000)

  private fun createJwtToken(username: String, expireIn: Int): String =
    JWT.create()
      .withAudience(audience)
      .withIssuer(issuer)
      .withClaim("username", username)
      .withExpiresAt(Date(System.currentTimeMillis() + expireIn))
      .sign(Algorithm.HMAC256(secret))


  fun customValidator(
    credential: JWTCredential,
  ): JWTPrincipal? {
    val username: String? = extractUsername(credential)
    val foundUser: User? = username?.let(userRepository::findByUsername)

    return foundUser?.let {
      if (audienceMatches(credential))
        JWTPrincipal(credential.payload)
      else
        null
    }
  }

  private fun audienceMatches(
    credential: JWTCredential,
  ): Boolean =
    credential.payload.audience.contains(audience)

  fun audienceMatches(
    audience: String
  ): Boolean =
    this.audience == audience

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()

  private fun extractUsername(credential: JWTCredential): String? =
    credential.payload.getClaim("username").asString()
}</pre>



<p>First of all, we replaced the <code>UserService</code> dependency from the constructor with the <code>UserRepository</code>. This way, we would avoid a circular dependency between this class and <code>UserService</code> later. </p>



<p>Then, we extracted the code responsible for generating JWT tokens into a separate function- <code>createJwtToken</code>&#8211; which will be called by the <code>createAccessToken</code>, and <code>createRefreshToken</code>. Please note that these two functions declare different expiration times for both token types- 1 hour for the access token and 24 for the refresh token. </p>



<p>But what expiration time should we set in a real-life? Well, again, it depends 🙂 And we must consider both security concerns, as well as the <strong>storage</strong>. Yes, the longer the expiration time, the bigger the amount of refresh tokens stored in our database.  </p>



<p>Lastly, we added the <code>audienceMatches</code> function, which we will use later in the code. </p>



<h2 class="wp-block-heading" id="h-update-routing-layer">Update Routing Layer</h2>



<p>With all of that done, let&#8217;s navigate to the <code>routing</code> package and make the necessary changes too. </p>



<h3 class="wp-block-heading" id="h-add-request-response-classes">Add Request/Response Classes </h3>



<p>As the first step, let&#8217;s add a class responsible for mapping refresh token call JSON payload:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenRequest(
  val token: String,
)</pre>



<p>Following, let&#8217;s implement a class, which will serialize the authentication response:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class AuthResponse(
  val accessToken: String,
  val refreshToken: String,
)</pre>



<p>And finally, the <code>RefreshTokenResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class RefreshTokenResponse(
  val token: String,
)</pre>



<h3 class="wp-block-heading" id="h-update-routing-kt">Update Routing.kt</h3>



<p>As the next step, let&#8217;s update the <code>Routing.kt</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  userService: UserService
) {
  routing {

    route("/api/auth") {
      authRoute(userService)
    }

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>As we can see, we don&#8217;t inject the <code>JwtService</code> anymore. Instead, we invoke the <code>authRoute</code> with <code>UserService</code> instance instead. </p>



<p><h3 class="wp-block-heading" id="h-update-routing-kt">Edit AuthRoute.kt</h3>With that done, let&#8217;s update the <code>AuthRoute</code> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.routing.request.LoginRequest
import com.codersee.routing.request.RefreshTokenRequest
import com.codersee.routing.response.AuthResponse
import com.codersee.routing.response.RefreshTokenResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Route.authRoute(userService: UserService) {

  post {
    val loginRequest = call.receive&lt;LoginRequest>()

    val authResponse: AuthResponse? = userService.authenticate(loginRequest)

    authResponse?.let {
      call.respond(authResponse)
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

  post("/refresh") {
    val request = call.receive&lt;RefreshTokenRequest>()

    val newAccessToken = userService.refreshToken(token = request.token)

    newAccessToken?.let {
      call.respond(
        RefreshTokenResponse(it)
      )
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

}</pre>



<p>So, instead of dealing with <code>JwtService</code>, we are going to invoke the <code>UserService</code> functions instead. </p>



<p>In the first handler for <code>POST /api/auth</code> calls, we invoke the <code>authenticate</code> method and if we receive an <code>AuthResponse</code>, then we will simply return that to the API client with <code>200 OK</code> status. Otherwise, we will return <code>401 Unauthorized</code>. </p>



<p>Additionally, we add a new handler for <code>POST /api/auth/refresh</code> calls. Inside it, we read the JSON payload and create an instance of <code>RefreshTokenRequest</code>. Then, we invoke the <code>refreshToken</code> method from <code>UserService</code> (which we are going to implement in a moment). And just like previously, if the service returns a non-null object, then we respond with <code>200 OK</code> and with <code>401 Unauthorized</code> if that&#8217;s not the case. </p>



<h2 class="wp-block-heading" id="h-update-userservice">Update UserService</h2>



<p>As the last thing in our Ktor with the JWT refresh token tutorial, we must update the <code>UserService</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.interfaces.DecodedJWT
import com.codersee.model.User
import com.codersee.repository.RefreshTokenRepository
import com.codersee.repository.UserRepository
import com.codersee.routing.request.LoginRequest
import com.codersee.routing.response.AuthResponse
import java.util.*

class UserService(
  private val userRepository: UserRepository,
  private val jwtService: JwtService,
  private val refreshTokenRepository: RefreshTokenRepository
) {

  fun findAll(): List&lt;User> =
    userRepository.findAll()

  fun findById(id: String): User? =
    userRepository.findById(
      id = UUID.fromString(id)
    )

  fun findByUsername(username: String): User? =
    userRepository.findByUsername(username)

  fun save(user: User): User? {
    val foundUser = userRepository.findByUsername(user.username)

    return if (foundUser == null) {
      userRepository.save(user)
      user
    } else null
  }

  fun authenticate(loginRequest: LoginRequest): AuthResponse? {
    val username = loginRequest.username
    val foundUser: User? = userRepository.findByUsername(username)

    return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
      val accessToken = jwtService.createAccessToken(username)
      val refreshToken = jwtService.createRefreshToken(username)

      refreshTokenRepository.save(refreshToken, username)

      AuthResponse(
        accessToken = accessToken,
        refreshToken = refreshToken,
      )
    } else
      null
  }

  fun refreshToken(token: String): String? {
    val decodedRefreshToken = verifyRefreshToken(token)
    val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

    return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
      val foundUser: User? = userRepository.findByUsername(persistedUsername)
      val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

      if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
        jwtService.createAccessToken(persistedUsername)
      else
        null
    } else
      null
  }

  private fun verifyRefreshToken(token: String): DecodedJWT? {
    val decodedJwt: DecodedJWT? = getDecodedJwt(token)

    return decodedJwt?.let {
      val audienceMatches = jwtService.audienceMatches(it.audience.first())

      if (audienceMatches)
        decodedJwt
      else
        null
    }
  }

  private fun getDecodedJwt(token: String): DecodedJWT? =
    try {
      jwtService.jwtVerifier.verify(token)
    } catch (ex: Exception) {
      null
    }
}</pre>



<p>And let&#8217;s see the most important changes here. </p>



<p>Firstly, we add the <code>authenticate</code> function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun authenticate(loginRequest: LoginRequest): AuthResponse? {
  val username = loginRequest.username
  val foundUser: User? = userRepository.findByUsername(username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password) {
    val accessToken = jwtService.createAccessToken(username)
    val refreshToken = jwtService.createRefreshToken(username)

    refreshTokenRepository.save(refreshToken, username)

    AuthResponse(
      accessToken = accessToken,
      refreshToken = refreshToken,
    )
  } else
    null
}</pre>



<p>This one is simply responsible for fetching a user from our repository using the passed <code>username</code>. </p>



<p></p>



<p>If we find it successfully, we check if the password matches (again, it&#8217;s just a dummy plain text comparison and you want to learn more about password checks in real-life scenarios). And if that&#8217;s the case, then we simply return the access and refresh tokens to the user and persist the refresh token in our &#8220;database&#8221;. </p>



<p>Nextly, we have a refresh token flow function: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun refreshToken(token: String): String? {
  val decodedRefreshToken = verifyRefreshToken(token)
  val persistedUsername = refreshTokenRepository.findUsernameByToken(token)

  return if (decodedRefreshToken != null &amp;&amp; persistedUsername != null) {
    val foundUser: User? = userRepository.findByUsername(persistedUsername)
    val usernameFromRefreshToken: String? = decodedRefreshToken.getClaim("username").asString()

    if (foundUser != null &amp;&amp; usernameFromRefreshToken == foundUser.username)
      jwtService.createAccessToken(persistedUsername)
    else
      null
  } else
    null
}</pre>



<p>When it comes to refreshing a token, we check if there is any persisted username found by the token value. If it is, then we fetch the user by the given username (this way, we won&#8217;t refresh a token for user that does not exist anymore). </p>



<p>And at this point, we can <strong>rerun </strong>our application and <strong>verify </strong>if our logic works, as expected. Again, if you&#8217;d like to get a ready-to-go Postman collection, then please let me know <strong>in the comments section</strong>.</p>



<h2 class="wp-block-heading" id="h-ktor-with-jwt-refresh-token-summary">Ktor With JWT Refresh Token Summary</h2>



<p>And basically, that&#8217;s all for this, second tutorial in a series related to Ktor security, in which we learned how to create a <strong>Ktor application with JWT refresh token flow</strong>. </p>



<p>As always, you can find the whole source code in my <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">GitHub repository</a>. </p>



<p>Thank you for being here and please do not forget to share it with other people on your social media (it will help me a lot) and check other posts from the series:</p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a>, </li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>
<p>The post <a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/">Secure Ktor app with JWT refresh tokens. Refresh token flow.</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/feed/</wfw:commentRss>
			<slash:comments>2</slash:comments>
		
		
			</item>
		<item>
		<title>Secure REST API with Ktor and JWT Access Tokens</title>
		<link>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/</link>
					<comments>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/#respond</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 21 Nov 2023 08:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008343</guid>

					<description><![CDATA[<p>In this, first article in a series, I will show you how to create a REST API with Ktor and secure it with JWT (JSON Web Token) access tokens.</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>Hello and welcome to the <strong>first article in a series</strong>. This time, I will show you how to implement a simple <strong>REST API with Ktor</strong> and secure it with <strong>JWT (JSON Web Token) access tokens</strong>.</p>



<p>To be even more specific, in this tutorial I will teach you:</p>



<ul class="wp-block-list">
<li>how to implement a simple <strong>REST API</strong>, </li>



<li>what are <strong>JSON Web Tokens</strong> and what does the <strong>authentication</strong> mean, </li>



<li>how to <strong>generate </strong>and <strong>validate JWT access tokens</strong>, and <strong>authenticate </strong>users, </li>



<li>and how to read data using the <strong>JWTPrincipal</strong>.</li>
</ul>



<p>In the upcoming articles, we will continue the topic and learn additionally: </p>



<ul class="wp-block-list">
<li><a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">how to implement a <strong>refresh token flow </strong>with Ktor</a>,</li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then check out my video:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FWmURqVIWZeQ%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-is-authentication">What Is Authentication? </h2>



<p>But before we start implementing our Ktor app with JWT tokens, let me quickly explain <strong>what exactly authentication is.</strong> </p>



<p>In essence, it is the process of <strong>verifying the identity</strong> of a user, system, or device <strong>to ensure that they are who they claim to be</strong>. </p>



<p>It is like having a secret code to enter a club. Let&#8217;s imagine we want to get into a cool party, and at the entrance, there&#8217;s a bouncer checking everyone&#8217;s membership. To prove we belong, we provide a special password. If the password matches what&#8217;s on the list, we&#8217;re in! </p>



<p>In the digital world, it&#8217;s the same idea &#8211; we need to confirm our identity with a unique key (like a password or JWT token) to access our email, bank account, or, like in our case, any resource through the exposed REST API. </p>



<p>Of course, <strong>authentication<em> </em></strong>is not a synonym for <strong>authorization</strong> (but we will get back to that in the upcoming post). </p>



<h2 class="wp-block-heading" id="h-jwt-tokens-explained">JWT Tokens Explained</h2>



<p>As the last thing before we head to the practice, let&#8217;s learn a bit about the JWT tokens. </p>



<p><strong>JWTs</strong>, also known as <strong>JSON Web Tokens</strong>, are nothing else than one of plenty of existing ways to authenticate users. </p>



<p>Just like with our previous example- in real life, we can use our ID, passport, or in other cases, some secret word. </p>



<p>Here, the JWT is like a digital secret handshake on the internet. When sign in using our username and password, the server gives us a special JWT, and later, we can use it to access a REST API. This way, we don&#8217;t need to specify such vulnerable data every time we want to make a request. </p>



<p>What are the other advantages? Well, they are <strong>safe and compact, </strong>and allow us to <strong>hold information</strong>. </p>



<p>JWTs consist of three parts separated by dots: header.payload.signature: </p>



<figure class="wp-block-image size-large"><img decoding="async" width="1024" height="534" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png" alt="The image is a screenshot from jwt.io page and present and example encoded JWT token with its decoded value." class="wp-image-9008239" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-300x156.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-768x400.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io.png 1191w" sizes="(max-width: 1024px) 100vw, 1024px" /></figure>



<p>The encoded value can be easily decoded and JWT token values can be read. </p>



<p>The <strong>header </strong>typically consists of two parts: the type of the token (missing above), and the signing algorithm being used (HMAC SHA512).</p>



<p>The second part of the token is the payload, which contains the <strong>claims</strong>. Claims, Claims are statements about an entity (typically, the user) and additional data.</p>



<p>Lastly, the <strong>signature</strong> is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn&#8217;t changed along the way.</p>



<h2 class="wp-block-heading" id="h-set-up-new-ktor-project">Set Up New Ktor Project</h2>



<p>With all of that said, let&#8217;s navigate to the <a href="https://start.ktor.io/" target="_blank" rel="noreferrer noopener">Ktor Project Generator</a> and start by specifying the necessary details: </p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="731" height="851" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1.png" alt="Image is a screenshot from the Ktor Project Generator page and shows the first step of generator settings, like Ktor version, engine set to Netty, or HOCON file config. " class="wp-image-9008354" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1.png 731w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_1-258x300.png 258w" sizes="auto, (max-width: 731px) 100vw, 731px" /></figure>



<p>At this stage, we must specify the basic things, like the Ktor version, engine, etc. </p>



<p>Nextly, let&#8217;s click the &#8220;Add plugins&#8221; button and configure the following plugins (aka dependencies):</p>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="630" height="750" src="http://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2.png" alt="Image shows all added plugins in the configurator: authentication, authentication JWT, content negotiation, kotlinx.serialization, and routing." class="wp-image-9008355" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2.png 630w, https://blog.codersee.com/wp-content/uploads/2023/11/ktor_jwt_authentication_generate_project_2-252x300.png 252w" sizes="auto, (max-width: 630px) 100vw, 630px" /></figure>



<p>As we can see, in order to expose a REST API and secure it with JWT in Ktor, we will need the following: </p>



<ul class="wp-block-list">
<li>Authentication,</li>



<li>Authentication JWT, </li>



<li>Content Negotiation,</li>



<li>kotlinx.serialization,</li>



<li>and Routing. </li>
</ul>



<p>With that done, let&#8217;s generate the project, download the ZIP file, extract it, and import it to the IntelliJ IDEA. </p>



<p>When we open up the project, we should see that there are some plugins already configured inside the <code>plugins</code> package and <code>Application.kt</code> file. </p>



<p>Let&#8217;s delete files inside the <code>plugins</code> and clean up the <code>Application.kt</code> to be on the same page:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
}</pre>



<h2 class="wp-block-heading" id="h-expose-users-api">Expose Users API </h2>



<p>As the next step, let&#8217;s expose the API responsible for <strong>user management</strong>.</p>



<h3 class="wp-block-heading" id="h-add-user-model">Add User Model</h3>



<p>Firstly, let&#8217;s create the <code>model</code> package and implement the <code>User.kt</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.UUID

data class User(
  val id: UUID,
  val username: String,
  val password: String
)</pre>



<p>As can be seen, it&#8217;s just a simple class with the <em>id, username, </em>and <em>password </em>fields.</p>



<h3 class="wp-block-heading" id="h-create-userrepository">Create UserRepository</h3>



<p>Secondly, we must add a class responsible for user storage and retrieval. </p>



<p>To do so, let&#8217;s add the <code>repository</code> package and put the <code>UserRepository</code> inside it:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import java.util.*

class UserRepository {

  private val users = mutableListOf&lt;User>()

  fun findAll(): List&lt;User> =
    users

  fun findById(id: UUID): User? =
    users.firstOrNull { it.id == id }

  fun findByUsername(username: String): User? =
    users.firstOrNull { it.username == username }

  fun save(user: User): Boolean =
    users.add(user)
}</pre>



<p>Normally, we would put a code responsible for &#8220;talking&#8221; to the database right here. </p>



<p>In our case, to not lose focus from the clue of this article- authentication in Ktor and JWT- we introduce a dummy repository, which uses the mutable list to store users in memory. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Note:</strong> in our dummy repository, we store user password in a plain text. <br><br>Nevertheless, in real-life scenarios we should always encrypt it before persisting. To learn a bit more about different encryption algorithms, you can check out my other post in which I explain <a href="https://blog.codersee.com/kotlin-pbkdf2-secure-password-hashing/">PBKDF2 hashing</a>.</cite></blockquote>



<h3 class="wp-block-heading" id="h-implement-userservice">Implement UserService</h3>



<p>With that done, let&#8217;s introduce a service layer in our project. </p>



<p>So, similarly, let&#8217;s add the <code>service</code> package and the <code>UserService</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.repository.UserRepository
import java.util.*

class UserService(
  private val userRepository: UserRepository
) {

  fun findAll(): List&lt;User> =
    userRepository.findAll()

  fun findById(id: String): User? =
    userRepository.findById(
      id = UUID.fromString(id)
    )

  fun findByUsername(username: String): User? =
    userRepository.findByUsername(username)

  fun save(user: User): User? {
    val foundUser = userRepository.findByUsername(user.username)

    return if (foundUser == null) {
      userRepository.save(user)
      user
    } else null
  }
}</pre>



<p>This time, we expose 4 functions, which we will utilize later when handling requests and working with JWT tokens. </p>



<h3 class="wp-block-heading" id="h-add-request-and-response-classes">Add Request and Response Classes</h3>



<p>Before we expose our REST endpoint, let&#8217;s take care of the request and response classes. </p>



<p>Let&#8217;s start by creating the <code>routing.request</code> package and implementing the <code>UserRequest</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class UserRequest(
  val username: String,
  val password: String,
)</pre>



<p>As we can see, we must annotate objects that we want to <strong>serialize</strong> or <strong>deserialize</strong> using the <strong>@Serializable</strong> when working with <code>kotlinx.serialization</code> library. </p>



<p>As a result, the serialization plugin will automatically generate the implementation of <em>KSerializer</em>. </p>



<p>Similarly, let&#8217;s add the <code>routing.response</code> package and the <code>UserResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class UserResponse(
  val id: UUID,
  val username: String,
)</pre>



<p>This one seems pretty similar, but <strong>unfortunately</strong>, we must do one more thing here.</p>



<h3 class="wp-block-heading" id="h-custom-uuid-kserializer">Custom UUID KSerializer</h3>



<p>If we would try to use this class as it is, we would end up with <strong>the following error:</strong></p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Serializer has not been found for type &#8216;UUID&#8217;. To use context serializer as fallback, explicitly annotate type or property with @Contextual.</cite></blockquote>



<p>And to fix this issue, <strong>we must implement our own UUID serializer</strong>.</p>



<p>So as the first step, let&#8217;s introduce the <code>util</code> package and add the <code>UUIDSerializer</code> object: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.KSerializer
import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor
import kotlinx.serialization.descriptors.SerialDescriptor
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
import java.util.*

object UUIDSerializer : KSerializer&lt;UUID> {

  override val descriptor: SerialDescriptor
    get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.STRING)

  override fun deserialize(decoder: Decoder): UUID =
    UUID.fromString(decoder.decodeString())

  override fun serialize(encoder: Encoder, value: UUID) =
    encoder.encodeString(value.toString())
}</pre>



<p>Following, let&#8217;s instruct the plugin to use our implementation whenever it wants to serialize the <code>id</code> field:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.util.UUIDSerializer
import kotlinx.serialization.Serializable
import java.util.*

@Serializable
data class UserResponse(
  @Serializable(with = UUIDSerializer::class)
  val id: UUID,
  val username: String,
)</pre>



<h3 class="wp-block-heading" id="h-register-serialization-plugin">Register Serialization Plugin</h3>



<p>When working with Ktor, we must explicitly specify the plugins we use. There is no component scan based on the dependencies which you may know from Spring Boot 🙂 </p>



<p>So as the next step, let&#8217;s add the <code>Seriaization.kt</code> to the <code>plugins</code> package: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import io.ktor.serialization.kotlinx.json.*
import io.ktor.server.application.*
import io.ktor.server.plugins.contentnegotiation.*

fun Application.configureSerialization() {
  install(ContentNegotiation) {
    json()
  }
}</pre>



<p>The <code>ContentNegotiation</code> plugin has two tasks: </p>



<ul class="wp-block-list">
<li>it negotiates the media types between the client and server, </li>



<li>and serializes/deserializes the content in a specific format. </li>
</ul>



<p>In our case, we can clearly see that we will be using the <code>JSON</code> format. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: other formats, like XML, CBOR, and ProtoBuf are supported, too. </cite></blockquote>



<p>Lastly, we must make use of our extension function inside the <code>Application.kt</code> file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSerialization
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  configureSerialization()
}</pre>



<h3 class="wp-block-heading" id="h-add-user-route">Add User Route</h3>



<p>With all of that being done, we can finally expose endpoints for user management. </p>



<p>As the first step, let&#8217;s implement the <code>Routing.kt</code> class inside the <code>routing</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  userService: UserService
) {
  routing {

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>This way, we instruct Ktor that whenever a request is made to the <code>/api/user**</code>, it should look for a handler inside the <code>userRoute</code> (which we will add in a moment). </p>



<p>Following, let&#8217;s get back to the <code>Application.kt</code> and register our routing: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSerialization
import com.codersee.repository.UserRepository
import com.codersee.routing.configureRouting
import com.codersee.service.UserService
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  val userRepository = UserRepository()
  val userService = UserService(userRepository)

  configureSerialization()
  configureRouting(userService)
}</pre>



<p>Nextly, let&#8217;s add the <code>UserRoute.kt</code> inside the <code>routing</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.routing.request.UserRequest
import com.codersee.routing.response.UserResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.*

fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

  get {
    val users = userService.findAll()

    call.respond(
      message = users.map(User::toResponse)
    )
  }

  get("/{id}") {
    val id: String = call.parameters["id"]
      ?: return@get call.respond(HttpStatusCode.BadRequest)

    val foundUser = userService.findById(id)
      ?: return@get call.respond(HttpStatusCode.NotFound)

    call.respond(
      message = foundUser.toResponse()
    )
  }
}

private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password
  )

private fun User.toResponse(): UserResponse =
  UserResponse(
    id = this.id,
    username = this.username,
  )</pre>



<p>And to be on the same page, let&#8217;s rephrase what is going on here:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.model.User
import com.codersee.routing.request.UserRequest
import com.codersee.routing.response.UserResponse
import com.codersee.service.UserService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.util.*

fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

}</pre>



<p>Firstly, we inject the instance of <code>UserService</code>, which we will use to save and retrieve users. </p>



<p>Nextly, we specify a handler for <strong>POST requests</strong>. </p>



<p>Inside this handler, we get the JSON payload sent by the API consumer as an instance of <em>UserRequest</em> with the <code>call.receive&lt;T&gt;()</code> function. Then, we invoke the <code>save</code> function and pass the instance of the <code>User</code> class, which we obtain using the <code>toModel()</code> extension function. If the <code>save</code> returns a <em>null</em> value, we return <code>400 Bad Request</code> with the <code>call.respond()</code> function. </p>



<p>On the other hand, if the user was saved successfully, we respond with a <code>201 Created</code> status and pass its identifier as the <code>id</code> header value with <code>call.response.header()</code> function. </p>



<p>Then, we have another handler responsible for <code>GET /api/user</code> requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">get {
  val users = userService.findAll()

  call.respond(
    message = users.map(User::toResponse)
  )
}</pre>



<p>It is responsible for fetching all users and returning them after mapping with <code>toResponse()</code> extension functions. </p>



<p>Lastly, we have the handler answering to the <code>GET /api/user/{id}</code> requests:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">get("/{id}") {
  val id: String = call.parameters["id"]
    ?: return@get call.respond(HttpStatusCode.BadRequest)

  val foundUser = userService.findById(id)
    ?: return@get call.respond(HttpStatusCode.NotFound)

  call.respond(
    message = foundUser.toResponse()
  )
}</pre>



<p>This time, we read the <strong>path variable</strong> <code>id</code> using the <code>call.paramaters</code>. If is not present, we return a <code>400 Bad Request</code> response. </p>



<p>Following, we try to find a user by ID and if we fail, then we simply return the <code>404 Not Found</code>. </p>



<h2 class="wp-block-heading" id="h-ktor-with-jwt">Ktor With JWT </h2>



<p>Excellent! At this point, we have a working API, so it&#8217;s finally time to learn a bit more about how to secure it, and how to generate JWT tokens in our Ktor application so that we can use them later to access these endpoints. </p>



<h3 class="wp-block-heading" id="h-update-config-file">Update Config File</h3>



<p>As the first step, let&#8217;s navigate to the <code>application.conf</code> file. </p>



<p>Among other settings, we should see the following <code>jwt</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">jwt {
    domain = "https://jwt-provider-domain/"
    audience = "jwt-audience"
    realm = "ktor sample app"
}</pre>



<p>Let&#8217;s slightly modify it:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">jwt {
    audience = "my-audience"
    issuer = "http://localhost/"
    realm = "My realm"
    secret = ${SECRET}
}</pre>



<p>Without going into too much detail, these values (except for the <strong>secret</strong>) will be <strong>claims</strong> in our JWT tokens: </p>



<ul class="wp-block-list">
<li><strong>audience</strong>&#8211; which describes the recipient of the token, </li>



<li><strong>issuer</strong>&#8211; which is the entity that issues the token,</li>



<li><strong>realm</strong>&#8211; which is an optional parameter providing additional context or scope,</li>



<li><strong>secret</strong>&#8211; which is a secret key used for token signing and verification.</li>
</ul>



<p>Please note that the <code>secret</code> value <strong>IS NOT </strong>hardcoded (and should never be). We will source it from the environment variable called <code>SECRET</code>.</p>



<h2 class="wp-block-heading" id="h-implement-jwtservice">Implement JwtService</h2>



<p>With that done, we can navigate to the <code>service</code> package and add the <code>JwtService</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userService: UserService,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  fun createJwtToken(loginRequest: LoginRequest): String? {
    val foundUser: User? = userService.findByUsername(loginRequest.username)

    return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password)
      JWT.create()
        .withAudience(audience)
        .withIssuer(issuer)
        .withClaim("username", loginRequest.username)
        .withExpiresAt(Date(System.currentTimeMillis() + 3_600_000))
        .sign(Algorithm.HMAC256(secret))
    else
      null
  }

  fun customValidator(
    credential: JWTCredential,
  ): JWTPrincipal? {
    val username: String? = extractUsername(credential)
    val foundUser: User? = username?.let(userService::findByUsername)

    return foundUser?.let {
      if (audienceMatches(credential))
        JWTPrincipal(credential.payload)
      else
        null
    }
  }

  private fun audienceMatches(
    credential: JWTCredential,
  ): Boolean =
    credential.payload.audience.contains(audience)

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()

  private fun extractUsername(credential: JWTCredential): String? =
    credential.payload.getClaim("username").asString()
}
</pre>



<p>Again, let&#8217;s break it down into smaller chunks: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.auth0.jwt.JWT
import com.auth0.jwt.JWTVerifier
import com.auth0.jwt.algorithms.Algorithm
import com.codersee.model.User
import io.ktor.server.application.*
import io.ktor.server.auth.jwt.*
import java.util.*

class JwtService(
  private val application: Application,
  private val userService: UserService,
) {

  private val secret = getConfigProperty("jwt.secret")
  private val issuer = getConfigProperty("jwt.issuer")
  private val audience = getConfigProperty("jwt.audience")

  val realm = getConfigProperty("jwt.realm")

  val jwtVerifier: JWTVerifier =
    JWT
      .require(Algorithm.HMAC256(secret))
      .withAudience(audience)
      .withIssuer(issuer)
      .build()

  private fun getConfigProperty(path: String) =
    application.environment.config.property(path).getString()
}</pre>



<p>Firstly, we inject the instance of <code>Application</code> and <code>UserService</code> classes. </p>



<p>Following we read the configuration properties we set in the previous step. 3 of them can be private, but the <code>realm</code> will be later used in another class to configure security. </p>



<p>After that, we must create an instance of the <code>JwtVerifier</code> and configure it. In simple words, this class holds the verify method which will later verify that a given token has not only a proper JWT format but also its signature matches. </p>



<p>And this is why we specify which <strong>algorithm</strong>, <strong>audience</strong>, and <strong>issuer</strong> a valid token must have.</p>



<p>With all of that done, we introduce the <em>createJwtToken</em> function:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun createJwtToken(loginRequest: LoginRequest): String? {
  val foundUser: User? = userService.findByUsername(loginRequest.username)

  return if (foundUser != null &amp;&amp; loginRequest.password == foundUser.password)
    JWT.create()
      .withAudience(audience)
      .withIssuer(issuer)
      .withClaim("username", loginRequest.username)
      .withExpiresAt(Date(System.currentTimeMillis() + 3_600_000))
      .sign(Algorithm.HMAC256(secret))
  else
    null
}</pre>



<p>This one takes the <code>username</code> as an argument and tries to find a user with it. </p>



<p>If we succeed and the password matches, then we produce a new JWT access token with the appropriate <strong>audience, issuer, </strong>and <strong>username</strong>, expiring in <strong>60 minutes</strong> and signed with <strong>the HMAC-SHA256</strong> algorithm (with our secret key).</p>



<p>Lastly, we add the <code>customValidator</code> function, which we will later use to add an <strong>additional validation:</strong></p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun customValidator(
  credential: JWTCredential,
): JWTPrincipal? {
  val username: String? = extractUsername(credential)
  val foundUser: User? = username?.let(userService::findByUsername)

  return foundUser?.let {
    if (audienceMatches(credential))
      JWTPrincipal(credential.payload)
    else
      null
  }
}

private fun audienceMatches(
  credential: JWTCredential,
): Boolean =
  credential.payload.audience.contains(audience)

private fun extractUsername(credential: JWTCredential): String? =
  credential.payload.getClaim("username").asString()</pre>



<p>As the first thing, we extract the <em>username</em> from the JWT token and we try to find a user by this value. </p>



<p>If we succeed, then we check if the <em>audience </em>from the token matches the audience set in our Ktor project. If that&#8217;s true, then we instantiate the <strong>JWTPrincipal</strong>, which we will use later to obtain the information. </p>



<h2 class="wp-block-heading" id="h-install-authentication-plugin">Install Authentication Plugin</h2>



<p>At this point, we have the JWT service ready, so it&#8217;s time to install the Authentication plugin. </p>



<p>To do so, let&#8217;s add the <code>Security</code> plugin inside the <code>plugins</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(
  jwtService: JwtService
) {
  authentication {
    jwt {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }
  }
}</pre>



<p>But what exactly is going on here? </p>



<p>The above code installs the Authentication plugin (<code>authentication</code>) with JWT Authentication provider (<code>jwt</code>). </p>



<p>Additionally, we must specify the <code>realm</code>, <code>verifier</code>, and the custom <code>validate</code> function that we implemented previously. </p>



<p>And what if we would like to have <strong>multiple providers in our project?</strong></p>



<p>Well, we can do that easily by simply specifying a new one with a name:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.auth.jwt.*

fun Application.configureSecurity(
  jwtService: JwtService
) {
  authentication {
    jwt {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }

    jwt("another-auth") {
      realm = jwtService.realm
      verifier(jwtService.jwtVerifier)

      validate { credential ->
        jwtService.customValidator(credential, jwtService)
      }
    }
  }
}</pre>



<p>Later, we will see how to make use of these JWT auth providers, but for now, let&#8217;s navigate to the <code>Application.kt</code> file and make use of our new config:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.plugins.configureSecurity
import com.codersee.plugins.configureSerialization
import com.codersee.repository.UserRepository
import com.codersee.routing.configureRouting
import com.codersee.service.JwtService
import com.codersee.service.UserService
import io.ktor.server.application.*

fun main(args: Array&lt;String>) {
  io.ktor.server.netty.EngineMain.main(args)
}

fun Application.module() {
  val userRepository = UserRepository()
  val userService = UserService(userRepository)
  val jwtService = JwtService(this, userService)

  configureSerialization()
  configureSecurity(jwtService)
  configureRouting(jwtService, userService)
}</pre>



<h2 class="wp-block-heading" id="h-expose-login-endpoint">Expose Login Endpoint</h2>



<p>Perfect! At this point we have everything we need to expose a new endpoint- <code>POST /api/auth</code>&#8211; which we will use to get JWT access tokens for our Ktor app based on the passed username and password. </p>



<p>So as the first step, let&#8217;s go to the <code>Routing</code> file, inject the <code>JwtService</code> and prepare a new route:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.service.JwtService
import com.codersee.service.UserService
import io.ktor.server.application.*
import io.ktor.server.routing.*

fun Application.configureRouting(
  jwtService: JwtService,
  userService: UserService
) {
  routing {

    route("/api/auth") {
      authRoute(jwtService)
    }

    route("/api/user") {
      userRoute(userService)
    }

  }
}</pre>



<p>Nextly, let&#8217;s implement the <code>LoginRequest</code> inside the <code>routing.request</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import kotlinx.serialization.Serializable

@Serializable
data class LoginRequest(
  val username: String,
  val password: String,
)</pre>



<p>No traps here this time, so let&#8217;s create the <code>AuthRoute.kt</code> file: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.routing.request.LoginRequest
import com.codersee.service.JwtService
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*

fun Route.authRoute(jwtService: JwtService) {

  post {
    val user = call.receive&lt;LoginRequest>()

    val token: String? = jwtService.createJwtToken(user.username)

    token?.let {
      call.respond(hashMapOf("token" to token))
    } ?: call.respond(
      message = HttpStatusCode.Unauthorized
    )
  }

}</pre>



<p>This time, we read the request payload to the <code>LoginRequest</code> instance and we try to create a new JWT access token based on the <em>username</em>. </p>



<p>If we succeed (meaning a user with the following username was found and the password matches), then we return a fresh JWT access token. Otherwise, we simply return <code>401 Unauthorized</code> to the API consumer.</p>



<p>At this point, we can test the app, for example by using curl (and if you would like to get a full Postman collection with automated token handling, then please leave a comment for this article).</p>



<p>Let&#8217;s start by creating a new user:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/api/user' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "user", 
    "password": "password"
}'</pre>



<p>As a response, we should get <code>201 Created</code> with the id in the response header. </p>



<p>Following, let&#8217;s try to get a JWT access token:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="generic" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">curl --location --request POST 'http://localhost:8080/api/auth' \
--header 'Content-Type: application/json' \
--data-raw '{
    "username": "user", 
    "password": "password"
}'

// Response:

{
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJhdWQiOiJteS1hdWRpZW5jZSIsImlzcyI6Imh0dHA6Ly9sb2NhbGhvc3QvIiwidXNlcm5hbWUiOiJ1c2VyIiwiZXhwIjoxNzAwMTU2Mzg4fQ.DG8SsNrQI5-VqrZeZMFL4IRuVSLeo9Zs9dEifti9nvQ"
}</pre>



<p>Excellent! So far so good. </p>



<p>You can even take this token and check it out using the <a href="https://jwt.io/">jwt.io page</a> 🙂</p>



<h2 class="wp-block-heading" id="h-secure-user-endpoint">Secure User Endpoint</h2>



<p>Finally, let&#8217;s secure user API endpoints: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun Route.userRoute(userService: UserService) {

  post {
    val userRequest = call.receive&lt;UserRequest>()

    val createdUser = userService.save(
      user = userRequest.toModel()
    ) ?: return@post call.respond(HttpStatusCode.BadRequest)

    call.response.header(
      name = "id",
      value = createdUser.id.toString()
    )
    call.respond(
      message = HttpStatusCode.Created
    )
  }

  authenticate {
    get {
      val users = userService.findAll()

      call.respond(
        message = users.map(User::toResponse)
      )
    }
  }

  authenticate("another-auth") {
    get("/{id}") {
      val id: String = call.parameters["id"]
        ?: return@get call.respond(HttpStatusCode.BadRequest)

      val foundUser = userService.findById(id)
        ?: return@get call.respond(HttpStatusCode.NotFound)

      if (foundUser.username != extractPrincipalUsername(call))
        return@get call.respond(HttpStatusCode.NotFound)

      call.respond(
        message = foundUser.toResponse()
      )
    }
  }
}

private fun UserRequest.toModel(): User =
  User(
    id = UUID.randomUUID(),
    username = this.username,
    password = this.password
  )

private fun User.toResponse(): UserResponse =
  UserResponse(
    id = this.id,
    username = this.username,
  )

private fun extractPrincipalUsername(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("username")
    ?.asString()
</pre>



<p>As we can see, after our changes, every <code>GET /api/user</code> request will be verified using the provider, which we specified without the name. </p>



<p>When we check logs, we should even see the confirmation: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Matched routes:<br>&#8220;&#8221; -&gt; &#8220;api&#8221; -&gt; &#8220;user&#8221; -&gt; &#8220;(authenticate &#8220;default&#8221;)&#8221; -&gt; &#8220;(method:GET)&#8221;</cite></blockquote>



<p>On the other hand, every request to <code>GET /api/user/{id}</code> will be handled by the <code>another-auth</code> provider: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>&#8220;&#8221; -&gt; &#8220;api&#8221; -&gt; &#8220;user&#8221; -&gt; &#8220;(authenticate another-auth)&#8221; -&gt; &#8220;{id}&#8221; -&gt; &#8220;(method:GET)&#8221;</cite></blockquote>



<p>Lastly, it&#8217;s worth mentioning that we changed one more thing: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">authenticate("another-auth") {
  get("/{id}") {
    val id: String = call.parameters["id"]
      ?: return@get call.respond(HttpStatusCode.BadRequest)

    val foundUser = userService.findById(id)
      ?: return@get call.respond(HttpStatusCode.NotFound)

    if (foundUser.username != extractPrincipalUsername(call))
      return@get call.respond(HttpStatusCode.NotFound)

    call.respond(
      message = foundUser.toResponse()
    )
  }
}


private fun extractPrincipalUsername(call: ApplicationCall): String? =
  call.principal&lt;JWTPrincipal>()
    ?.payload
    ?.getClaim("username")
    ?.asString()</pre>



<p>In this handler, we obtain the <code>JWTPrincipal</code> (created inside the JwtService) in order to read the <code>username</code>. If it matches with the found user, then we simply return the user response. But if not, then we return <code>404 Not Found</code>. </p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial on how to implement a <strong>REST API with Ktor and Kotlin</strong> and how to secure it with <strong>JWT access tokens</strong>.</p>



<p>If you would like to find the whole source code, then check out <a href="https://github.com/codersee-blog/kotlin-ktor-jwt-access-tokens-authentication" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p>Lastly, if you enjoyed this tutorial, then please do not forget to share it with other people on your social media (it will help me a lot) and check out the continuation for this post:</p>



<ul class="wp-block-list">
<li>h<a href="https://blog.codersee.com/ktor-app-with-jwt-refresh-token-flow/" target="_blank" rel="noreferrer noopener">ow to implement a <strong>refresh token flow </strong>with Ktor</a>,</li>



<li>and <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/" target="_blank" rel="noreferrer noopener">how to add a<strong> role-based access control (RBAC)</strong> to the project</a>. </li>
</ul>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/">Secure REST API with Ktor and JWT Access Tokens</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/secure-rest-api-with-ktor-jwt-access-tokens/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</title>
		<link>https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/</link>
					<comments>https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Thu, 26 Oct 2023 05:30:09 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[Access Tokens]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[JSON Web Tokens]]></category>
		<category><![CDATA[Refresh Tokens]]></category>
		<category><![CDATA[Spring Boot 3]]></category>
		<category><![CDATA[Spring Security]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008217</guid>

					<description><![CDATA[<p>In this article, I will show you how to implement a secure REST API with Spring Boot 3, Spring Security, Kotlin, and JWT tokens.</p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/">Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>If you would like to learn how to secure a REST API with <strong>Spring Boot 3</strong> (<strong>Spring Security 6</strong>), <strong>Kotlin</strong>, and <strong>JWT tokens</strong>, then you came to the right place 😉 </p>



<p>In this, comprehensive guide I will show you step-by-step how to:</p>



<ul class="wp-block-list">
<li>authenticate and authorize users, </li>



<li>assign and verify roles, </li>



<li>generate JWT tokens,</li>



<li>and implement a refresh token flow.</li>
</ul>



<p>And everything that with our beloved Kotlin programming language! </p>



<h2 class="wp-block-heading" id="h-video-tutorial">Video Tutorial</h2>



<p>If you prefer <strong>video content</strong>, then you can find this tutorial as a YouTube playlist <a href="https://www.youtube.com/playlist?list=PLvN8k8yxjoeud4ESoB-wjiieqYGaDVqPR" target="_blank" rel="noreferrer noopener">right here</a>. </p>



<p>Alternatively, you can start from the introduction:</p>



<div style="text-align: center; width: 90%; margin-left: 5%;">
<a href="https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/"><img decoding="async" src="https://blog.codersee.com/wp-content/plugins/wp-youtube-lyte/lyteCache.php?origThumbUrl=%2F%2Fi.ytimg.com%2Fvi%2FPCIkfv6cC0k%2Fhqdefault.jpg" alt="YouTube Video"></a><br /><br /></p></div>



<p>If you find this content useful,<strong> please leave a subscription&nbsp;</strong> 😉</p>



<h2 class="wp-block-heading" id="h-what-exactly-will-we-implement">What Exactly Will We Implement? </h2>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image aligncenter size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="665" src="http://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-1024x665.png" alt="Image shows sticky notes presenting what exactly will be implemented in this tutorial. " class="wp-image-9008335" style="aspect-ratio:1.5398496240601505;width:805px;height:auto" srcset="https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-1024x665.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-300x195.png 300w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api-768x499.png 768w, https://blog.codersee.com/wp-content/uploads/2023/11/articles_users_auth_api.png 1287w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p></p>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<p>Well, in our project we will implement a REST API for <strong>articles</strong> and <strong>users</strong>. </p>



<p><strong>Articles</strong> will be visible to any user who successfully authenticates using the JWT access token. </p>



<p><strong>Users </strong>endpoint, on the other hand, will be accessible <strong>only for admin users</strong> in our system (with one exception- a public endpoint for creating new users). </p>



<p>Of course, we will expose additional authentication endpoints, which will take data from users, like <code>email</code> and <code>password</code> and generate access and refresh tokens for our system. </p>



<p>Long story short- I believe this project will be simple enough to easily understand the topic, and comprehensive enough to cover the most popular real-life cases. </p>



<h2 class="wp-block-heading" id="h-a-tiny-bit-of-theory">A Tiny Bit Of Theory</h2>



<p>In this paragraph, I&#8217;d like to cover what exactly JWT tokens are, the difference between authorization and authentication, and present from a bird&#8217;s eye view how Spring Security works. </p>



<p>So, if you feel that you understand these and came here for the practice, then please skip to the next chapter 😉 </p>



<h3 class="wp-block-heading" id="h-authentication-vs-authorization">Authentication vs Authorization </h3>



<p>I am pretty sure that you heard both terms plenty of times already and even heard people using them alternately. </p>



<p>Nevertheless, although similar, these two terms refer to completely different things. </p>



<p>Imagine you&#8217;re at the entrance of a super-secret club:</p>



<p><strong>Authentication</strong> is like showing your ID to prove you are who you say you are. It&#8217;s the process of confirming your identity. So, in our club scenario, it&#8217;s like showing your driver&#8217;s license to the bouncer.</p>



<p><strong>Authorization</strong>, on the other hand, is like being allowed into different parts of the club based on your VIP status. Once you&#8217;re inside (thanks to authentication), authorization decides what you can and cannot do. For example, VIP members might access the VIP lounge, while regular guests can&#8217;t.</p>



<h3 class="wp-block-heading" id="h-jwt-tokens">JWT Tokens </h3>



<p>Among plenty of existing ways to authenticate users, JWTs (JSON Web Tokens) are one of the most popular ones. </p>



<p>They are like a digital secret handshake on the internet. When we log into a website, the server gives us a special JWT. This token is like a digital badge that says, &#8220;Hey, this person is legit!&#8221; </p>



<p>Why are they so cool? Well, they are <strong>safe, compact, </strong>and allow us to <strong>hold information</strong>. </p>



<p>JWTs consist of three parts separated by dots: header.payload.signature: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="534" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png" alt="The image is a screenshot from jwt.io page and present and example encoded JWT token with its decoded value." class="wp-image-9008239" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-1024x534.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-300x156.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io-768x400.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_io.png 1191w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>As we can see, the encoded value can be easily decoded and JWT token values can be read. </p>



<p>The <strong>header </strong>typically consists of two parts: the type of the token (missing above), and the signing algorithm being used (HMAC SHA512).</p>



<p>The second part of the token is the payload, which contains the <strong>claims</strong>. Claims, Claims are statements about an entity (typically, the user) and additional data.</p>



<p>Lastly, the <strong>signature</strong> is used to verify that the sender of the JWT is who it says it is and to ensure that the message wasn&#8217;t changed along the way.</p>



<h3 class="wp-block-heading" id="h-bird-s-eye-view-on-spring-security">Bird&#8217;s Eye View on Spring Security</h3>



<p>At this point, we know the difference between authentication and authorization and also what JWT tokens are. Awesome! </p>



<p>So, now I&#8217;ll try to do the impossible- explain how Spring Security works in a few sentences 🙂 </p>



<p><strong>Spring Security</strong> is like the security team of a building. It manages authentication and authorization for our app with:</p>



<ol class="wp-block-list">
<li><strong>Filters</strong>&#8211; think of security filters as checkpoints at different doors in the building. When a request comes in, it goes through these filters. These filters handle tasks like authentication and authorization. For instance, there might be a filter that checks if you have a proper access card (authentication), and another that ensures you can enter specific rooms (authorization).</li>



<li><strong>Authentication</strong>&#8211; when you log in, Spring Security checks your credentials (like username and password). If they match, you&#8217;re authenticated. Spring Security uses authentication providers, which can be a database, LDAP, or any other source, to verify your identity.</li>



<li><strong>Security Context</strong>&#8211; once authenticated, your security details are stored in the Security Context. It&#8217;s like being given a special pass after passing through the checkpoint. This pass (security context) contains your roles and permissions.</li>



<li><strong>Authorization</strong>&#8211; now, when you try to access a specific part of the application, Spring Security checks your roles and permissions stored in the security context. If you&#8217;re authorized (based on your roles), you&#8217;re allowed in. Otherwise, you might be denied access.</li>



<li><strong>Customization</strong>&#8211; Spring Security allows you to customize the security filters, authentication providers, and access rules according to your application&#8217;s requirements. You can configure which URLs need authentication, what roles are required, etc.</li>
</ol>



<p> And that&#8217;s <strong>basically what we&#8217;re gonna learn today</strong>.</p>



<h2 class="wp-block-heading" id="h-import-spring-boot-3-project">Import Spring Boot 3 Project</h2>



<p>As the first step, let&#8217;s generate a new Spring Boot 3 project. </p>



<p>To do so, we can use the <a href="https://start.spring.io/" target="_blank" rel="noreferrer noopener">Spring Initializr</a> page:</p>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="509" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-1024x509.png" alt="Image is a screenshot from Spring Initializr page and shows a minimum project configuration." class="wp-image-9008218" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-1024x509.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-300x149.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1-768x382.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_1.png 1502w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<div style="height:35px" aria-hidden="true" class="wp-block-spacer"></div>



<p>As we can see, in this project, we will use <em>Spring Boot 3.1.5</em>, <em>Kotlin</em> as a programming language, and <em>Kotlin DSL</em> for our Gradle build config. </p>



<p>At this point, the only dependency required to expose a REST API is <em>Spring Web</em>. We will add Spring Security, and libraries necessary for JWT tokens later (and we will see why later, too). </p>



<p>With all of that done, we can hit the <strong>generate</strong> button, download the zip file, and import it to IntelliJ. </p>



<h2 class="wp-block-heading" id="h-expose-articles-api">Expose Articles API </h2>



<p>Following, let&#8217;s implement the logic necessary to expose our first endpoint- <code>GET /api/article</code> .</p>



<p>In the future, this endpoint will be accessible only to users with a valid JWT token. </p>



<h3 class="wp-block-heading" id="h-create-article-model">Create Article Model</h3>



<p>As the first step, let&#8217;s introduce a new package- <code>model</code> &#8211; and create a new data class- <code>Article</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.*

data class Article(
  val id: UUID,
  val title: String,
  val content: String,
)</pre>



<p>This class consists of three example fields simulating a real-life article. </p>



<h3 class="wp-block-heading" id="h-implement-article-repository">Implement Article Repository</h3>



<p>Secondly, we must introduce a new class responsible for article retrieval and storage. </p>



<p>Let&#8217;s add the <code>repository</code> package and implement <code>ArticleRepository</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.Article
import org.springframework.stereotype.Repository
import java.util.UUID

@Repository
class ArticleRepository {

  private val articles = listOf(
    Article(id = UUID.randomUUID(), title= "Article 1", content= "Content 1"),
    Article(id = UUID.randomUUID(), title= "Article 2", content= "Content 2"),
  )

  fun findAll(): List&lt;Article> =
    articles

}</pre>



<p>In our example, articles are nothing else, than a hardcoded private list of items. </p>



<p>Additionally, we expose the <code>findAll()</code> method, which encapsulates the list and which we will be invoking from outside the class. </p>



<p>Of course, I did it this way to keep it simple and so that we do not lose focus on what matters in this tutorial- Spring Boot security with Kotlin and JWT tokens. </p>



<p>Nevertheless, you can easily adjust this project to your needs. In real life, this would be the class where you can put logic responsible for communication with your database. </p>



<h3 class="wp-block-heading" id="h-create-service">Create Service</h3>



<p>As the next step, let&#8217;s create the <code>service</code> package and introduce the <code>ArticleService</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.Article
import com.codersee.jwtauth.repository.ArticleRepository
import org.springframework.stereotype.Service

@Service
class ArticleService(
  private val articleRepository: ArticleRepository
) {

  fun findAll(): List&lt;Article> =
    articleRepository.findAll()
}</pre>



<p>As we can see, this class exposes the <code>findAll()</code> function and does nothing else than invoking the <code>findAll()</code> method from our repository. </p>



<p>Again, this might look a bit like an <strong>overengineering</strong>. But trust me, keeping a well-organized project from the very beginning will pay off in the future. </p>



<h3 class="wp-block-heading" id="h-expose-article-rest-api">Expose Article REST API </h3>



<p>Lastly, let&#8217;s add the <code>controller.article</code> package and put two classes in it. </p>



<p>Let&#8217;s start with the <code>ArticleResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.*

data class ArticleResponse(
  val id: UUID,
  val title: String,
  val content: String,
)</pre>



<p>Before returning any article from our REST API, we will map model objects into the response objects. </p>



<p>And with that done, let&#8217;s implement the <code>ArticleController</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.Article
import com.codersee.jwtauth.service.ArticleService
import org.springframework.web.bind.annotation.*
import java.util.*

@RestController
@RequestMapping("/api/article")
class ArticleController(
  private val articleService: ArticleService
) {

  @GetMapping
  fun listAll(): List&lt;ArticleResponse> =
    articleService.findAll()
      .map { it.toResponse() }

  private fun Article.toResponse(): ArticleResponse =
    ArticleResponse(
      id = this.id,
      title = this.title,
      content = this.content,
    )
}</pre>



<p>As we can see, we must annotate our controller class with the <em>@RestController </em>annotation in order to expose a REST API in Spring Boot. </p>



<p>Additionally, we add the <code>@RequestMapping</code> annotation so that whenever we hit the <code>/api/article</code> endpoint, Spring knows that this is the class it should use to handle the request. </p>



<p>Lastly, we mark the <code>listAll()</code> function with <code>@GetMapping</code>, and convert all found <em>Article</em> instances to the <em>ArticleResponse.</em> </p>



<p>Later, when we add the JWT security to our Spring Boot Kotlin project, these endpoints will be accessible only for <code>ADMIN</code> users with one exception- the endpoint responsible for creating new users- which will be publicly exposed.   </p>



<h3 class="wp-block-heading" id="h-add-user-model">Add User Model</h3>



<p>With that said, let&#8217;s add the <code>User.kt</code> file to the <code>model</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.*

data class User(
  val id: UUID,
  val email: String,
  val password: String,
  val role: Role
)

enum class Role {
  USER, ADMIN
}</pre>



<p>As we can see, apart from the <code>id</code>, the User class contains three important fields: <code>email</code>, <code>password</code>, and <code>role</code>, which we will extensively when dealing with JWT tokens. </p>



<h3 class="wp-block-heading" id="h-implement-userrepository">Implement UserRepository</h3>



<p>Following, let&#8217;s create the <code>UserRepository</code> in the <code>repository</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.Role
import com.codersee.jwtauth.model.User
import org.springframework.stereotype.Repository
import java.util.*

@Repository
class UserRepository {

  private val users = mutableSetOf(
    User(
      id = UUID.randomUUID(),
      email = "email-1@gmail.com",
      password = "pass1",
      role = Role.USER,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-2@gmail.com",
      password = "pass2",
      role = Role.ADMIN,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-3@gmail.com",
      password = "pass3",
      role = Role.USER,
    ),
  )

  fun save(user: User): Boolean =
    users.add(user)

  fun findByEmail(email: String): User? =
    users
      .firstOrNull { it.email == email }

  fun findAll(): Set&lt;User> =
    users

  fun findByUUID(uuid: UUID): User? =
    users
      .firstOrNull { it.id == uuid }

  fun deleteByUUID(uuid: UUID): Boolean {
    val foundUser = findByUUID(uuid)

    return foundUser?.let {
      users.removeIf {
        it.id == uuid
      }
    } ?: false
  }
}</pre>



<p>Similarly to our previous repository- we simply introduce a mutable list, which we can edit with a bunch of util methods. </p>



<p>And don&#8217;t worry about the password stored in plain text- we will get back to that topic, too 🙂 </p>



<h3 class="wp-block-heading" id="h-add-userservice">Add UserService</h3>



<p>Nextly, we must go to the <code>service</code> package and introduce this <code>UserService</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.User
import com.codersee.jwtauth.repository.UserRepository
import org.springframework.stereotype.Service
import java.util.*

@Service
class UserService(
  private val userRepository: UserRepository
) {

  fun createUser(user: User): User? {
    val found = userRepository.findByEmail(user.email)

    return if (found == null) {
      userRepository.save(user)
      user
    } else null
  }

  fun findByUUID(uuid: UUID): User? =
    userRepository.findByUUID(uuid)

  fun findAll(): List&lt;User> =
    userRepository.findAll()
      .toList()

  fun deleteByUUID(uuid: UUID): Boolean =
    userRepository.deleteByUUID(uuid)
}</pre>



<p>Nothing spectacular here, so let&#8217;s move to the next step. </p>



<h3 class="wp-block-heading" id="h-expose-user-rest-api">Expose User REST API </h3>



<p>Just like with articles, let&#8217;s start with introducing request/response objects. </p>



<p>This time, we will need both the <code>UserRequest</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class UserRequest(
  val email: String,
  val password: String,
)</pre>



<p>And the <code>UserResponse</code>, which will be serialized into JSON objects:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import java.util.*

data class UserResponse(
  val uuid: UUID,
  val email: String,
)</pre>



<p>And with that done, we can add the <code>UserController</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.model.Role
import com.codersee.jwtauth.model.User
import com.codersee.jwtauth.service.UserService
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.*
import org.springframework.web.server.ResponseStatusException
import java.util.*

@RestController
@RequestMapping("/api/user")
class UserController(
  private val userService: UserService
) {

  @PostMapping
  fun create(@RequestBody userRequest: UserRequest): UserResponse =
    userService.createUser(userRequest.toModel())
      ?.toResponse()
      ?: throw ResponseStatusException(HttpStatus.BAD_REQUEST, "Cannot create user.")

  @GetMapping
  fun listAll(): List&lt;UserResponse> =
    userService.findAll()
      .map { it.toResponse() }

  @GetMapping("/{uuid}")
  fun findByUUID(@PathVariable uuid: UUID): UserResponse =
    userService.findByUUID(uuid)
      ?.toResponse()
      ?: throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found.")


  @DeleteMapping("/{uuid}")
  fun deleteByUUID(@PathVariable uuid: UUID): ResponseEntity&lt;Boolean> {
    val success = userService.deleteByUUID(uuid)

    return if (success)
      ResponseEntity.noContent()
        .build()
    else
      throw ResponseStatusException(HttpStatus.NOT_FOUND, "User not found.")
  }

  private fun User.toResponse(): UserResponse =
    UserResponse(
      uuid = this.id,
      email = this.email,
    )

  private fun UserRequest.toModel(): User =
    User(
      id = UUID.randomUUID(),
      email = this.email,
      password = this.password,
      role = Role.USER,
    )
}</pre>



<p>As we can see, the logic above is pretty similar to what we&#8217;ve done before. </p>



<p>We mark our class with <code>@RestController</code> and <code>@RequestMapping</code>, functions with annotations matching HTTP Methods they are gonna handle, and also our path variables and request bodies. </p>



<p>When converting between models/requests/responses, we make use of <strong>extension functions</strong>, which are my favorite approach for mappers in Kotlin. </p>



<p>Lastly, whenever we want to return anything else than <code>200 OK</code>, we use the <strong>ResponseStatusException</strong>, which is a clean way to do so. </p>



<p>If you would like to test this API now, then check out the following <a href="https://drive.google.com/file/d/1eNG-bOqBmepaZQDZbNpywyh5sWSeazt2/view?usp=sharing" target="_blank" rel="noreferrer noopener">Postman collection</a>.</p>



<h2 class="wp-block-heading" id="h-import-spring-security-and-jwt-library">Import Spring Security and JWT Library</h2>



<p>Wonderful. At this point, we have everything we need to finally add Spring Security to our Spring Boot Kotlin project, and start implementing <strong>JWT authentication</strong> and <strong>authorization</strong>.</p>



<h3 class="wp-block-heading" id="h-update-build-gradle-kts">Update build.gradle.kts</h3>



<p>As the first step, let&#8217;s add the following to the <code>build.gradle.kts</code> file:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">dependencies {
	// other imports
	implementation("io.jsonwebtoken:jjwt-api:0.12.3")
	implementation("io.jsonwebtoken:jjwt-impl:0.12.3")
	implementation("io.jsonwebtoken:jjwt-jackson:0.12.3")
	implementation("org.springframework.boot:spring-boot-starter-security")
	testImplementation("org.springframework.security:spring-security-test")
}</pre>



<p>As we can see, in order to secure our Spring Boot 3 REST API, we must add not only the Spring Security but also an additional library to deal with JWT tokens. </p>



<p>Although the Spring security is a must-have, please feel free to choose whatever JWT library suits you best (you can find a full list <a href="https://jwt.io/libraries" target="_blank" rel="noreferrer noopener">here</a>). I have decided to go with <code>io.jsonwebtoken</code> which is the most popular Java library.</p>



<h3 class="wp-block-heading" id="h-sync-and-rerun-project">Sync and Rerun Project</h3>



<p>Following, let&#8217;s sync our Gradle project and rerun our application. </p>



<p>Among other logs, we will see the following lines: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="raw" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">Using generated security password: 52d7d5b8-ce49-4d93-a4e1-721780290e58

This generated password is for development use only. Your security configuration must be updated before running your application in production.</pre>



<p>Moreover, when we try to query our endpoints, we will get <code>401 Unauthorized</code> for each request. </p>



<p>But what happened? </p>



<p>Well, whenever we add Spring Security to our Spring Boot project, the <strong>authentication gets enabled by default. </strong></p>



<p>In other words, we can query our endpoint now using the <strong>basic access authentication</strong> with <code>user</code> as a username and the randomly generated password as the <code>password</code>. </p>



<p>As a proof, when we update the <code>Authorization</code> in our Postman collection, we will start seeing results again: </p>



<figure class="wp-block-image size-large"><img loading="lazy" decoding="async" width="1024" height="350" src="http://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-1024x350.png" alt="Image presents Postman request using the Basic Auth as the authorization mechanism. " class="wp-image-9008223" srcset="https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-1024x350.png 1024w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-300x102.png 300w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2-768x262.png 768w, https://blog.codersee.com/wp-content/uploads/2023/10/spring_boot_3_security_jwt_2.png 1163w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<h2 class="wp-block-heading" id="h-create-jwt-tokens">Create JWT Tokens</h2>



<p>Although basic auth may be a good choice in some cases, in this tutorial, I would like to show you how to authenticate and authorize requests using <strong>JWT access tokens</strong>. </p>



<p>So, let&#8217;s start everything by implementing a service responsible for token operations. </p>



<h3 class="wp-block-heading" id="h-edit-application-yaml-file">Edit application.yaml file</h3>



<p>As the first step, let&#8217;s navigate to the <code>resources</code> folder and edit the <code>application.yaml</code> file: </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Note: when generating a new Spring Boot project, the <code>application.properties</code> file is created automatically. So before we start, let&#8217;s change the extension to <code>yaml</code>.</cite></blockquote>



<pre class="EnlighterJSRAW" data-enlighter-language="yaml" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">jwt:
  key: ${JWT_KEY}
  access-token-expiration: 3600000
  refresh-token-expiration: 86400000</pre>



<p>As we can see, we introduce 3 custom properties: the key and expiration time (in milliseconds) for both access and refresh tokens. </p>



<p>The key is value will be sourced from the <code>JWT_TOKEN</code> environment variable, which is nothing else than a randomly generated String value. Later, we will see where and why we need it. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite><strong>Important:</strong> vulnerable values should never be hardcoded when implementing a Spring Boot (and not only) application. <br><br>Regardless of the environment the app is running in, values should be stored securely and sourced to our application using environment variables. <br></cite></blockquote>



<h3 class="wp-block-heading" id="h-make-use-of-configurationproperties">Make Use Of @ConfigurationProperties</h3>



<p>As the next step, let&#8217;s make use of the <em>@ConfigurationProperties</em> to easily convert our properties into Kotlin objects.</p>



<p>Let&#8217;s start by adding the <code>configuration</code> package and the <code>JwtProperties</code> class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("jwt")
data class JwtProperties(
  val key: String,
  val accessTokenExpiration: Long,
  val refreshTokenExpiration: Long,
)</pre>



<p>If you&#8217;d like to learn more about @ConfigurationProperties, then I have <a href="https://blog.codersee.com/fail-spring-boot-app-on-missing-environment-variables/" target="_blank" rel="noreferrer noopener">another article</a> you might want to check out later. </p>



<p>Additionally, let&#8217;s add the <code>Configuration</code> class: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Configuration


@Configuration
@EnableConfigurationProperties(JwtProperties::class)
class Configuration</pre>



<p>This way, we enable support for beans annotated with @ConfigurationProperties in our project. </p>



<h3 class="wp-block-heading" id="h-add-tokenservice">Add TokenService</h3>



<p>With all of that done, let&#8217;s add the <code>TokenService</code> class: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Service
class TokenService(
  jwtProperties: JwtProperties
) {

  private val secretKey = Keys.hmacShaKeyFor(
    jwtProperties.key.toByteArray()
  )

  fun generate(
    userDetails: UserDetails,
    expirationDate: Date,
    additionalClaims: Map&lt;String, Any> = emptyMap()
  ): String =
    Jwts.builder()
      .claims()
      .subject(userDetails.username)
      .issuedAt(Date(System.currentTimeMillis()))
      .expiration(expirationDate)
      .add(additionalClaims)
      .and()
      .signWith(secretKey)
      .compact()

  fun isValid(token: String, userDetails: UserDetails): Boolean {
    val email = extractEmail(token)

    return userDetails.username == email &amp;&amp; !isExpired(token)
  }

  fun extractEmail(token: String): String? =
    getAllClaims(token)
      .subject

  fun isExpired(token: String): Boolean =
    getAllClaims(token)
      .expiration
      .before(Date(System.currentTimeMillis()))

  private fun getAllClaims(token: String): Claims {
    val parser = Jwts.parser()
      .verifyWith(secretKey)
      .build()

    return parser
      .parseSignedClaims(token)
      .payload
  }
}</pre>



<p>As we can see, this class exposes functions, which we can use to deal with JWT access and refresh tokens. </p>



<p>Let&#8217;s walk quickly through our logic: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">private val secretKey = Keys.hmacShaKeyFor(
  jwtProperties.key.toByteArray()
)</pre>



<p>The <code>secretKey</code> is nothing else than an instance of <code>SecretKey</code>, which we will use to <strong>sign</strong> and <strong>verify</strong> JWT tokens in our system. In our case, we will use the <code>HMAC-SHA</code> algorithm based on the random key we provided to our project. </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun generate(
  userDetails: UserDetails,
  expirationDate: Date,
  additionalClaims: Map&lt;String, Any> = emptyMap()
): String =
  Jwts.builder()
    .claims()
    .subject(userDetails.username)
    .issuedAt(Date(System.currentTimeMillis()))
    .expiration(expirationDate)
    .add(additionalClaims)
    .and()
    .signWith(secretKey)
    .compact()</pre>



<p>The <code>generate</code> function is responsible for creating a serialized, URL-safe String with JWT tokens. We set the <code>subject</code>, expiration date, and additional claims based on the arguments passed to it. </p>



<p>Lastly, we sign the token using the <code>SecretKey</code> instance created in the previous step. </p>



<p>Additionally, we provide more functions which we will use later to extract values from tokens and validate them:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">fun isValid(token: String, userDetails: UserDetails): Boolean {
  val email = extractEmail(token)

  return userDetails.username == email &amp;&amp; !isExpired(token)
}

fun extractEmail(token: String): String? =
  getAllClaims(token)
    .subject

fun isExpired(token: String): Boolean =
  getAllClaims(token)
    .expiration
    .before(Date(System.currentTimeMillis()))

private fun getAllClaims(token: String): Claims {
  val parser = Jwts.parser()
    .verifyWith(secretKey)
    .build()

  return parser
    .parseSignedClaims(token)
    .payload
}</pre>



<h2 class="wp-block-heading" id="h-implement-custom-userdetailsservice">Implement Custom UserDetailsService</h2>



<p>As the next step, let&#8217;s introduce a custom implementation of <code>UserDetailsService</code>.</p>



<p>But what exactly is <code>UserDetailsService</code> and <code>UserDetails</code>? </p>



<p>Well, the <strong>UserDetails</strong> represents the core user information. In our case, we will use the default implementation called <code>User</code>, which holds the default information, such as username, password, and a collection of granted authorities (roles). </p>



<p>The <strong>UserDetailsService</strong> is nothing else than an interface used to load user-specific data. It is used by Spring Security to interact with our data source and validate users during authentication. </p>



<p>With that said, let&#8217;s add a new class- <code>CustomUserDetailsService</code>&#8211; to the <code>service</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.repository.UserRepository
import org.springframework.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.core.userdetails.UsernameNotFoundException
import org.springframework.stereotype.Service

typealias ApplicationUser = com.codersee.jwtauth.model.User

@Service
class CustomUserDetailsService(
  private val userRepository: UserRepository
) : UserDetailsService {

  override fun loadUserByUsername(username: String): UserDetails =
    userRepository.findByEmail(username)
      ?.mapToUserDetails()
      ?: throw UsernameNotFoundException("Not found!")

  private fun ApplicationUser.mapToUserDetails(): UserDetails =
    User.builder()
      .username(this.email)
      .password(this.password)
      .roles(this.role.name)
      .build()
}</pre>



<p>As we can see, both the <code>username</code> and <code>email</code> in our system refer to the same thing, which is used to uniquely identify users. </p>



<p>And in order to fetch <code>UserDetails</code> we must find the user in our repository and map it to the <code>User</code> instance. </p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p></p>
<cite>Additionally, we make use of the <code>typealias</code> which is a great way to avoid fully qualified name (we have both User in our <code>model</code> package and User from Spring Security</cite></blockquote>



<h2 class="wp-block-heading" id="h-update-configuration">Update Configuration </h2>



<p>With that done, let&#8217;s get back to the <code>Configuration</code> class and register a few beans:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.repository.UserRepository
import com.codersee.jwtauth.service.CustomUserDetailsService
import org.springframework.boot.context.properties.EnableConfigurationProperties
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.authentication.dao.DaoAuthenticationProvider
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder
import org.springframework.security.crypto.password.PasswordEncoder

@Configuration
@EnableConfigurationProperties(JwtProperties::class)
class Configuration {

  @Bean
  fun userDetailsService(userRepository: UserRepository): UserDetailsService =
    CustomUserDetailsService(userRepository)

  @Bean
  fun encoder(): PasswordEncoder = BCryptPasswordEncoder()

  @Bean
  fun authenticationProvider(userRepository: UserRepository): AuthenticationProvider =
    DaoAuthenticationProvider()
      .also {
        it.setUserDetailsService(userDetailsService(userRepository))
        it.setPasswordEncoder(encoder())
      }

  @Bean
  fun authenticationManager(config: AuthenticationConfiguration): AuthenticationManager =
    config.authenticationManager
}</pre>



<p>Firstly, we must register the bean of type <code>UserDetailsService</code>, which we introduced in the previous step. </p>



<p>Following, we register a <code>PasswordEncoder</code>. To put it simply, <strong>we should never store passwords</strong> in plain text. They should be always <strong>encrypted</strong>, or <strong>hashed</strong>. In this tutorial, we will use the <code>BCrypt</code> strong hashing function for that purpose. </p>



<p>Following, we must provide the <code>AuthenticationProvider</code> and configure which UserDetailsService we will use and the password encoder. </p>



<h2 class="wp-block-heading" id="h-update-userrepository">Update UserRepository</h2>



<p>In the previous step, I mentioned that we should never store passwords in plain text. </p>



<p>So, to fix that, let&#8217;s get back to the <code>UserRepository</code> and make the necessary changes:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">@Repository
class UserRepository(
  private val encoder: PasswordEncoder
) {

  private val users = mutableSetOf(
    User(
      id = UUID.randomUUID(),
      email = "email-1@gmail.com",
      password = encoder.encode("pass1"),
      role = Role.USER,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-2@gmail.com",
      password = encoder.encode("pass2"),
      role = Role.ADMIN,
    ),
    User(
      id = UUID.randomUUID(),
      email = "email-3@gmail.com",
      password = encoder.encode("pass3"),
      role = Role.USER,
    ),
  )

  fun save(user: User): Boolean {
    val updated = user.copy(password = encoder.encode(user.password))

    return users.add(updated)
  }
}</pre>



<p>As we can see, after this step both our predefined users and all users which we will create will have their passwords hashed. </p>



<h2 class="wp-block-heading" id="h-implement-authenticationfilter">Implement AuthenticationFilter</h2>



<p>Uff, quite a lot of preparation must be done before we secure our Spring Boot 3 Kotlin app with JWT tokens, isn&#8217;t it? </p>



<p>But don&#8217;t worry, we&#8217;re almost there. </p>



<p>As the next step, let&#8217;s navigate to the <code>config</code> package and introduce our custom Filter:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.service.CustomUserDetailsService
import com.codersee.jwtauth.service.TokenService
import jakarta.servlet.FilterChain
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter

@Component
class JwtAuthenticationFilter(
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
) : OncePerRequestFilter() {

  override fun doFilterInternal(
    request: HttpServletRequest,
    response: HttpServletResponse,
    filterChain: FilterChain
  ) {
    val authHeader: String? = request.getHeader("Authorization")

    if (authHeader.doesNotContainBearerToken()) {
      filterChain.doFilter(request, response)
      return
    }

    val jwtToken = authHeader!!.extractTokenValue()
    val email = tokenService.extractEmail(jwtToken)

    if (email != null &amp;&amp; SecurityContextHolder.getContext().authentication == null) {
      val foundUser = userDetailsService.loadUserByUsername(email)

      if (tokenService.isValid(jwtToken, foundUser))
        updateContext(foundUser, request)

      filterChain.doFilter(request, response)
    }
  }

  private fun String?.doesNotContainBearerToken() =
    this == null || !this.startsWith("Bearer ")

  private fun String.extractTokenValue() =
    this.substringAfter("Bearer ")

  private fun updateContext(foundUser: UserDetails, request: HttpServletRequest) {
    val authToken = UsernamePasswordAuthenticationToken(foundUser, null, foundUser.authorities)
    authToken.details = WebAuthenticationDetailsSource().buildDetails(request)
    SecurityContextHolder.getContext().authentication = authToken
  }

}</pre>



<p>I know, quite a lot of logic, but I&#8217;ll explain it step-by-step in a moment. </p>



<p>But before that, let&#8217;s understand <strong>what are Filters in Spring Framework.</strong> </p>



<p>Well, every request made to our Spring Framework application goes through the filter chain, where each filter in the chain can perform some operations on the request or response.</p>



<p>In our case, we want to use this feature to <strong>authenticate </strong>requests made to our REST API. We want to check whether a user sent a JWT token and validate it, and if everything is fine, we want to update the Spring Security Context. </p>



<p>And that&#8217;s exactly what&#8217;s happening in the code above. </p>



<p>Firstly, we check if the request <strong>contains an Authorization header</strong>. If no, we do not proceed with this function and we simply pass the request down the filter chain.  </p>



<p>Nextly, we extract the JWT token. A valid <code>Authorization</code> header value consists of the <code>Bearer &lt;JWT&gt;</code>, so we must extract the token itself. </p>



<p>Following, we read the <code>email</code> value from the token. And when we make sure it is not null and there&#8217;s no previously authenticated principal in the security context, we fetch the <code>UserDetails</code>, which we then use to validate them with the token. </p>



<p>Lastly, we simply update the security context in our system with <code>foundUser</code> (which is <code>UserDetails</code>) and authorities (which, in our case will be either <code>ADMIN</code> or <code>USER</code>).</p>



<p>To sum up, this function <strong>will be invoked once per each request.</strong> In simple terms, it will check the JWT token and if everything is fine, it will update the Spring Security context with information about user and its roles.</p>



<h2 class="wp-block-heading" id="h-add-security-configuration">Add Security Configuration</h2>



<p>At this point, we did everything we needed to to in order to <strong>authenticate the user</strong>. </p>



<p>In this step, we will learn how to <strong>authorize</strong> him.</p>



<p>And to do so, let&#8217;s introduce a new class- <code>SecurityConfiguration</code> &#8211; in the <code>config</code> package:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpMethod
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.http.SessionCreationPolicy
import org.springframework.security.web.DefaultSecurityFilterChain
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter

@Configuration
@EnableWebSecurity
class SecurityConfiguration(
  private val authenticationProvider: AuthenticationProvider
) {

  @Bean
  fun securityFilterChain(
    http: HttpSecurity,
    jwtAuthenticationFilter: JwtAuthenticationFilter
  ): DefaultSecurityFilterChain {
    http
      .csrf { it.disable() }
      .authorizeHttpRequests {
        it
          .requestMatchers("/api/auth", "api/auth/refresh", "/error")
          .permitAll()
          .requestMatchers(HttpMethod.POST, "/api/user")
          .permitAll()
          .requestMatchers("/api/user**")
          .hasRole("ADMIN")
          .anyRequest()
          .fullyAuthenticated()
      }
      .sessionManagement {
        it.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
      }
      .authenticationProvider(authenticationProvider)
      .addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter::class.java)

    return http.build()
  }
}</pre>



<p>As we can see, the above config exposes a new bean of type <code>DefaultSecurityFilterChain</code>. </p>



<p>To put it simply, this is the way we can modify the default chain by adding, removing, or replacing filters to tailor the security configuration according to our needs. </p>



<p>The first thing we do is <strong>disable the CSRF protection</strong>. It&#8217;s enabled by default, and in most cases not needed (but in real-life projects, please learn a bit more about CSRF attacks and whether your codebase is vulnerable). </p>



<p>Following, we configure the <strong>authorization</strong>. We can clearly see that requests to &#8220;/api/auth&#8221;, &#8220;api/auth/refresh&#8221;, &#8220;/error&#8221;, and POST requests to &#8220;/api/user&#8221; will be <strong>accessible without a token</strong>. And that&#8217;s correct- we cannot require a valid access token for users, who want to sign in, who want to create a new account, and when the API client wants to refresh the token. </p>



<p>And if you are wondering why is the &#8220;/error&#8221; in this list, too, then here comes the answer. It is because of the way Spring handles errors internally. Without that, every exception we throw in our codebase <strong>will return 403 Forbidden</strong>, instead of the HTTP status code we provided. </p>



<p>When it comes to the rest of the &#8220;/api/user&#8221; requests- we want to allow only users with role <code>ADMIN</code>.</p>



<p>And what with the rest of the requests? Well, we want them to be accessed only by fully authenticated users. But what does it mean? In our, stateless REST API, it means simply that every user with a valid JWT token will be able to access them (regardless of his role). </p>



<p>After that, we informed Spring that it should never create a HttpSession (we want our security to be stateless) and that it should never use it to obtain the SecurityContext.</p>



<p>And lastly, we register our <code>CustomAuthenticationProvider</code> and let Spring Security know that <code>JwtAuthenticationFilter</code> we implemented previously should be added before the default <code>UsernamePasswordAuthenticationFilter</code> (the one that required us to specify basic auth).</p>



<h2 class="wp-block-heading" id="h-expose-login-endpoint-generate-access-token">Expose Login Endpoint &#8211; Generate Access Token</h2>



<p>With all of that done, we can finally expose the endpoint, which will be used by our REST API consumers to get the <strong>JWT access token</strong>.</p>



<p>Let&#8217;s create the <code>config.auth</code> package and add a new controller class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.service.AuthenticationService
import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping("/api/auth")
class AuthController(
  private val authenticationService: AuthenticationService
) {

  @PostMapping
  fun authenticate(
    @RequestBody authRequest: AuthenticationRequest
  ): AuthenticationResponse =
    authenticationService.authentication(authRequest)

}</pre>



<p>As we can see, this controller uses <code>AuthenticationService</code> which we will implement in a moment.</p>



<p>But before we do so, let&#8217;s add the <code>AuthenticationRequest</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class AuthenticationRequest(
  val email: String,
  val password: String,
)</pre>



<p>And the response class:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class AuthenticationResponse(
  val accessToken: String,
)</pre>



<p>After that, let&#8217;s get back to the <code>service</code> package and implement a service, which will be responsible for authentication and token generation:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.controller.auth.AuthenticationRequest
import com.codersee.jwtauth.controller.auth.AuthenticationResponse
import com.codersee.jwtauth.controller.config.JwtProperties
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Service
import java.util.*

@Service
class AuthenticationService(
  private val authManager: AuthenticationManager,
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
  private val jwtProperties: JwtProperties,
) {

  fun authentication(authenticationRequest: AuthenticationRequest): AuthenticationResponse {
    authManager.authenticate(
      UsernamePasswordAuthenticationToken(
        authenticationRequest.email,
        authenticationRequest.password
      )
    )

    val user = userDetailsService.loadUserByUsername(authenticationRequest.email)

    val accessToken = createAccessToken(user)

    return AuthenticationResponse(
      accessToken = accessToken,
    )
  }

  private fun createAccessToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getAccessTokenExpiration()
  )

  private fun getAccessTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.accessTokenExpiration)

}</pre>



<p>As we can see, the first thing we do is invoke the <code>authenticate</code> method from <code>AuthenticationManager</code>. </p>



<p>If the <code>email</code> and <code>password</code> values don&#8217;t match with any of the users in our system, the <code>authenticate</code> method will throw <code>AuthenticationException</code> and the API will return <code>403 Forbidden</code>.</p>



<p>On the other hand, when they match, we will fetch the <code>UserDetails</code> and use it to generate a JWT token with an expiration time set in <code>application.yaml</code></p>



<p>At the moment, I highly encourage you to run our application and try to call endpoints with different cases. If you would like to use a ready-to-go Postman collection, then you can find one with all endpoints in the next chapter about refresh tokens. </p>



<h2 class="wp-block-heading" id="h-jwt-refresh-token">JWT Refresh Token</h2>



<p>Excellent! At this point, our REST API is properly secured and utilizing the JWT access tokens. </p>



<p>But depending on your needs, you may want to introduce the <strong>JWT refresh tokens </strong>to the system.</p>



<h3 class="wp-block-heading" id="h-introduce-new-endpoint">Introduce New Endpoint </h3>



<p>Firstly, let&#8217;s introduce a new endpoint and update our response classes. </p>



<p>Let&#8217;s start by adding a new <code>TokenResponse</code>:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class TokenResponse(
  val token: String
)</pre>



<p>We will use this class to return the <strong>refreshed access token.</strong></p>



<p>Following, let&#8217;s make the necessary changes to the <code>AuthenticationResponse</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class AuthenticationResponse(
  val accessToken: String,
  val refreshToken: String,
)</pre>



<p>As we can see, the <code>refreshToken</code> value will be returned when a user authenticates successfully along with the JWT token value. </p>



<p>After that, let&#8217;s add the <code>RefreshTokenRequest</code>, which will be used to deserialize refresh token sent by a user:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">data class RefreshTokenRequest(
  val token: String
)</pre>



<p>Great! </p>



<p>At this point, we can add a new endpoint:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">  @PostMapping("/refresh")
  fun refreshAccessToken(
    @RequestBody request: RefreshTokenRequest
  ): TokenResponse =
    authenticationService.refreshAccessToken(request.token)
      ?.mapToTokenResponse()
      ?: throw ResponseStatusException(HttpStatus.FORBIDDEN, "Invalid refresh token.")

  private fun String.mapToTokenResponse(): TokenResponse =
    TokenResponse(
      token = this
    )</pre>



<p>Don&#8217;t worry about the missing <code>refreshAccessToken</code> method, we will get back to it in a moment.</p>



<h3 class="wp-block-heading" id="h-implement-refresh-token-repository">Implement Refresh Token Repository</h3>



<p>As the next step, let&#8217;s go to the <code>repository</code> package and introduce the <code>RefreshTokenRepository</code>: </p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component

@Component
class RefreshTokenRepository {

  private val tokens = mutableMapOf&lt;String, UserDetails>()

  fun findUserDetailsByToken(token: String) : UserDetails? =
    tokens[token]

  fun save(token: String, userDetails: UserDetails) {
    tokens[token] = userDetails
  }

}</pre>



<p>As we can see, this class will be responsible for persisting and retrieving refresh tokens. </p>



<p>Along with our tokens, we will store the associated <code>UserDetails</code> instances, so that later we could match the associated subject (email). </p>



<h3 class="wp-block-heading" id="h-edit-authenticationservice">Edit AuthenticationService</h3>



<p>As the last thing, we must get back to the <code>AuthenticationService</code> and make the necessary changes:</p>



<pre class="EnlighterJSRAW" data-enlighter-language="kotlin" data-enlighter-theme="" data-enlighter-highlight="" data-enlighter-linenumbers="" data-enlighter-lineoffset="" data-enlighter-title="" data-enlighter-group="">import com.codersee.jwtauth.controller.auth.AuthenticationRequest
import com.codersee.jwtauth.controller.auth.AuthenticationResponse
import com.codersee.jwtauth.controller.config.JwtProperties
import com.codersee.jwtauth.repository.RefreshTokenRepository
import org.springframework.security.authentication.AuthenticationManager
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Service
import java.util.*

@Service
class AuthenticationService(
  private val authManager: AuthenticationManager,
  private val userDetailsService: CustomUserDetailsService,
  private val tokenService: TokenService,
  private val jwtProperties: JwtProperties,
  private val refreshTokenRepository: RefreshTokenRepository,
) {

  fun authentication(authenticationRequest: AuthenticationRequest): AuthenticationResponse {
    authManager.authenticate(
      UsernamePasswordAuthenticationToken(
        authenticationRequest.email,
        authenticationRequest.password
      )
    )

    val user = userDetailsService.loadUserByUsername(authenticationRequest.email)

    val accessToken = createAccessToken(user)
    val refreshToken = createRefreshToken(user)

    refreshTokenRepository.save(refreshToken, user)

    return AuthenticationResponse(
      accessToken = accessToken,
      refreshToken = refreshToken
    )
  }

  fun refreshAccessToken(refreshToken: String): String? {
    val extractedEmail = tokenService.extractEmail(refreshToken)

    return extractedEmail?.let { email ->
      val currentUserDetails = userDetailsService.loadUserByUsername(email)
      val refreshTokenUserDetails = refreshTokenRepository.findUserDetailsByToken(refreshToken)

      if (!tokenService.isExpired(refreshToken) &amp;&amp; refreshTokenUserDetails?.username == currentUserDetails.username)
        createAccessToken(currentUserDetails)
      else
        null
    }
  }

  private fun createAccessToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getAccessTokenExpiration()
  )

  private fun createRefreshToken(user: UserDetails) = tokenService.generate(
    userDetails = user,
    expirationDate = getRefreshTokenExpiration()
  )

  private fun getAccessTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.accessTokenExpiration)

  private fun getRefreshTokenExpiration(): Date =
    Date(System.currentTimeMillis() + jwtProperties.refreshTokenExpiration)
}</pre>



<p>Let&#8217;s summarize what exactly has changed here. </p>



<p>Firstly, we imported the <code>RefreshTokenRepository</code>, so that we could persist new tokens and retrieve the saved ones. </p>



<p>Moreover, we must generate a new refresh token whenever a user authenticates successfully. And that&#8217;s why we added the <code>getRefreshTokenExpiration</code> function and two additional lines in the <code>authentication</code> method. </p>



<p>When it comes to the refresh token flow, it can be summarized in the following steps: </p>



<ol class="wp-block-list">
<li>We extract the user <code>email</code> from the passed refresh token. </li>



<li>If this step is completed successfully, we fetch the <strong>current</strong> user details by the value. </li>



<li>After that, we look for the <strong>persisted</strong> user details with the refresh token. </li>



<li>Lastly, if the refresh token is not expired and the email from JWT subject matches the <strong>current</strong> user details, then a new <strong>access token</strong> is generated for that user. </li>



<li>Otherwise, we return null, which will be translated to <code>403 Forbidden</code> in our controller. </li>
</ol>



<p>And basically, that&#8217;s all 🙂 </p>



<p>If you would like to use a ready-to-go Postman collection with auh headers, then you can <a href="https://drive.google.com/file/d/1fXZ2Wa672rgNAKbwtBDo-r4f_yF0-Z1w/view?usp=sharing" target="_blank" rel="noreferrer noopener">find it right here</a>.</p>



<h2 class="wp-block-heading" id="h-summary">Summary</h2>



<p>And that&#8217;s all for this tutorial in which we have learned how to secure the REST API with Spring Boot 3 (Spring Security 6) with JWT access and refresh tokens and Kotlin. Good job! </p>



<p>If you would like to get the source code for this lesson, then check out <a href="https://github.com/codersee-blog/kotlin-spring-boot-3-spring-security-jwt-access-refresh-tokens" target="_blank" rel="noreferrer noopener">this GitHub repository</a>.</p>



<p>Lastly, if you enjoyed this one, then do not forget to leave a comment and check out my <a href="https://codersee.com/the-complete-kotlin-course/">Complete Kotlin Course</a> 🙂 </p>
<p>The post <a href="https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/">Spring Boot 3 (Spring Security 6) with Kotlin &#038; JWT</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/spring-boot-3-spring-security-6-with-kotlin-jwt/feed/</wfw:commentRss>
			<slash:comments>8</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-13 10:07:38 by W3 Total Cache
-->