<?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>JWT Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/jwt/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:50:29 +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>JWT Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<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 fetchpriority="high" 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 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="(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 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="(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>Keycloak with Spring Boot and Kotlin- Introduction</title>
		<link>https://blog.codersee.com/keycloak-with-spring-boot-and-kotlin-introduction/</link>
					<comments>https://blog.codersee.com/keycloak-with-spring-boot-and-kotlin-introduction/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Thu, 19 Nov 2020 07:56:30 +0000</pubDate>
				<category><![CDATA[Spring]]></category>
		<category><![CDATA[JWT]]></category>
		<category><![CDATA[Keycloak]]></category>
		<category><![CDATA[Spring Boot]]></category>
		<guid isPermaLink="false">http://codersee.com/?p=1625</guid>

					<description><![CDATA[<p>In this step by step guide, I will show you how to set up a Keycloak server and connect to it using Spring Boot and Kotlin.</p>
<p>The post <a href="https://blog.codersee.com/keycloak-with-spring-boot-and-kotlin-introduction/">Keycloak with Spring Boot and Kotlin- Introduction</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></description>
										<content:encoded><![CDATA[<h2 class="article-heading-introduction">1. Introduction</h2>
<p>Hello and welcome to the first article in a series showing how to use <strong>Keycloak with Spring Boot</strong> applications.</p>
<p>In this step by step guide, I will show you how to set up a <strong>Keycloak server</strong> and connect to it using <strong>Spring Boot and Kotlin</strong>. Additionally, you will learn how to secure <strong>REST</strong> endpoints with <strong>Keycloak</strong> combined with the <em><strong>@PreAuthorize</strong></em> annotation.</p>
<h2 class="article-heading-main">2. What is Keycloak?</h2>
<p>According to the <a href="https://www.keycloak.org/" target="_blank" rel="noopener noreferrer">documentation</a>, <strong>Keycloak</strong> is the Open Source Identity and Access Management solution. In simple terms, it allows us to add authentication and secure our applications with minimum effort. With Keycloak, plenty of topics, like user storage, authentication, and many many more, are available out of the box.</p>
<p>In addition, the <em><strong>keycloak-spring-boot-starter</strong></em> makes all the process of configuration really straightforward, thus saving our precious time.</p>
<h2 class="article-heading-main">3. Install Keycloak</h2>
<p>As the first step in this tutorial will be the installation of the <strong>Keycloak server</strong>. There are plenty of ways to achieve that, and I highly recommend you visit the <a href="https://www.keycloak.org/getting-started" target="_blank" rel="noopener noreferrer">official documentation</a> to choose the most suitable one for you.</p>
<p>To keep this tutorial as simple as possible, I&#8217;ve decided to deploy it as a Docker container. If you have the Docker environment already installed on your machine, all you need to do is to type the following command:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">docker run -p 8090:8080 -e KEYCLOAK_USER=admin -e KEYCLOAK_PASSWORD=admin -e DB_VENDOR=h2 quay.io/keycloak/keycloak:11.0.3
</pre>
<p>With this command, we create a container using the <strong>keycloak:11.0.3</strong> image and publish it&#8217;s <strong>8080</strong> port to the <strong>8180</strong> port of the host. Additionally, we pass the <strong>KEYCLOAK_USER</strong> and <strong>KEYCLOAK_PASSWORD</strong> environment variables for the initial<em> admin user (root)</em>.</p>
<p>However, the simplicity comes with its own restrictions. By default, the Keycloak server runs using the<strong> H2 database</strong>, so all the configuration will be lost after the container is terminated. To prevent that we can export a realm configuration to the <strong>JSON file</strong> (I will show you this step later).</p>
<p>&nbsp;</p>
<p><a href="https://codersee.com/newsletter/"><img loading="lazy" decoding="async" class="aligncenter wp-image-3002956 size-large" src="http://blog.codersee.com/wp-content/uploads/2022/05/join_newsletter-1024x576.png" alt="Image shows two ebooks people can get for free after joining newsletter" width="800" height="419" /></a></p>
<p>&nbsp;</p>
<h2 class="article-heading-main">3. Configure Keycloak</h2>
<p>Once the Keycloak server is<em> up and running</em>, let&#8217;s head to the administration console. All we need to do is to type <em><strong>http://localhost:8090</strong></em> in the address bar of the browser. We should see the following page:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1629 size-full" title="The image shows a home page of the Keycloak server" src="http://blog.codersee.com/wp-content/uploads/2020/11/1-home-view.png" alt="The image shows a home page of the Keycloak server" width="1129" height="659" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/1-home-view.png 1129w, https://blog.codersee.com/wp-content/uploads/2020/11/1-home-view-300x175.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/1-home-view-1024x598.png 1024w, https://blog.codersee.com/wp-content/uploads/2020/11/1-home-view-768x448.png 768w" sizes="auto, (max-width: 1129px) 100vw, 1129px" /></p>
<p>On the next page, we should see the login form. Let&#8217;s enter the credentials for the <em>admin</em> account we&#8217;ve already configured:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1630 size-full" title="The image shows the Keycloak login page" src="http://blog.codersee.com/wp-content/uploads/2020/11/2-login-page.png" alt="The image shows the Keycloak login page" width="666" height="587" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/2-login-page.png 666w, https://blog.codersee.com/wp-content/uploads/2020/11/2-login-page-300x264.png 300w" sizes="auto, (max-width: 666px) 100vw, 666px" /></p>
<h3 class="article-heading-sub">3.1. Create Realm</h3>
<p>After we&#8217;ve authenticated successfully, we can create a new <strong>realm</strong>. In Keycloak,<strong> realms</strong> manage a set of users, credentials, roles, and groups. Each user belongs to and logs into some realm. Moreover, they are completely isolated environments, which introduce an additional layer of security to our application.</p>
<p>As the next step, let&#8217;s navigate to the <em><strong>Add realm</strong></em> button:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1631 size-full" title="The image shows the location of the Create Realm button inside the Keycloak admin panel" src="http://blog.codersee.com/wp-content/uploads/2020/11/3-create-realm-button.png" alt="The image shows the location of the Create Realm button inside the Keycloak admin panel" width="647" height="508" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/3-create-realm-button.png 647w, https://blog.codersee.com/wp-content/uploads/2020/11/3-create-realm-button-300x236.png 300w" sizes="auto, (max-width: 647px) 100vw, 647px" /></p>
<p>On the next page, let&#8217;s type the name for the new realm and hit the create button:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1632 size-full" title="Image shows the add Keycloak realm form" src="http://blog.codersee.com/wp-content/uploads/2020/11/4-add-realm-form.png" alt="Image shows the add Keycloak realm form" width="541" height="245" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/4-add-realm-form.png 541w, https://blog.codersee.com/wp-content/uploads/2020/11/4-add-realm-form-300x136.png 300w" sizes="auto, (max-width: 541px) 100vw, 541px" /></p>
<p>Please remember this name, we will need it to<strong> configure the Spring Boot application later</strong>.</p>
<h3 class="article-heading-sub">3.2. Create Roles</h3>
<p>Nextly, let&#8217;s create some <strong>roles</strong> for our future users. Let&#8217;s navigate to the <em><strong>Roles</strong></em> tab on the left sidebar. On the next screen, we should see the list of already existing roles. Let&#8217;s click the <em><strong>Add role</strong> </em>button placed in the right part of the screen.</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1633 size-full" title="Image shows Keycloak realm roles list" src="http://blog.codersee.com/wp-content/uploads/2020/11/5-roles-list.png" alt="Image shows Keycloak realm roles list" width="905" height="273" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/5-roles-list.png 905w, https://blog.codersee.com/wp-content/uploads/2020/11/5-roles-list-300x90.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/5-roles-list-768x232.png 768w" sizes="auto, (max-width: 905px) 100vw, 905px" /></p>
<p>Firstly, let&#8217;s type the <strong>ROLE_USER</strong> and click save. After that, let&#8217;s add the <strong>ROLE_ADMIN </strong>which will be a composite role associated with the previous one:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1634 size-full" title="Image shows how to set up the admin role in Keycloak" src="http://blog.codersee.com/wp-content/uploads/2020/11/6-role-admin-setup.png" alt="Image shows how to set up the admin role in Keycloak" width="1144" height="648" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/6-role-admin-setup.png 1144w, https://blog.codersee.com/wp-content/uploads/2020/11/6-role-admin-setup-300x170.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/6-role-admin-setup-1024x580.png 1024w, https://blog.codersee.com/wp-content/uploads/2020/11/6-role-admin-setup-768x435.png 768w" sizes="auto, (max-width: 1144px) 100vw, 1144px" /></p>
<p>Please notice <strong>two important things</strong> here:</p>
<ul>
<li>A <strong>composite role</strong> combines together multiple roles. To put it simply- a user with the <strong>ROLE_ADMIN</strong> will be able to access all endpoints restricted for <strong>ROLE_USER</strong>. It helps to clean the architecture of the security a lot, however, please be careful not to introduce an additional gap to the system when using it.</li>
<li>Both roles have been prefixed with a <em><strong>ROLE_</strong></em>. Spring Security will automatically prefix <em><strong>hasRole</strong></em> checks with the same value.</li>
</ul>
<h3 class="article-heading-sub">3.3. Create Clients</h3>
<p>As the next step, let&#8217;s create <strong>clients</strong>. Most often, <strong>Keycloak clients</strong> are the applications and services we would like to secure, or which are obtaining tokens to access other applications.</p>
<p>For the purpose of this tutorial, we will create two clients. Let&#8217;s start the one, we will use to configure our<strong> Spring Boot</strong> application:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1635 size-full" title="Image shows how to set up the Keycloak client for the Spring Boot application" src="http://blog.codersee.com/wp-content/uploads/2020/11/7-create-spring-boot-client.png" alt="Image shows how to set up the Keycloak client for the Spring Boot application" width="1014" height="767" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/7-create-spring-boot-client.png 1014w, https://blog.codersee.com/wp-content/uploads/2020/11/7-create-spring-boot-client-300x227.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/7-create-spring-boot-client-768x581.png 768w" sizes="auto, (max-width: 1014px) 100vw, 1014px" /></p>
<p>Please notice, that we&#8217;ve set the Access Type to <em><strong>bearer-only</strong></em>. It means, that our application will not initiate a login, it&#8217;ll just pass received tokens to verify them with Keycloak. Most of the time, you will set this type of access type for the <strong>Spring Boot backends</strong>, which don&#8217;t need to call another microservices secured by Keycloak, or to use a service account.</p>
<p>Nextly, let&#8217;s create the <em><strong>public</strong></em> client, which will be used to get access tokens on behalf of the user:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1636 " title="Image shows how to create public client in Keycloak" src="http://blog.codersee.com/wp-content/uploads/2020/11/8-create-public-client.png" alt="Image shows how to create public client in Keycloak" width="1065" height="758" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/8-create-public-client.png 1065w, https://blog.codersee.com/wp-content/uploads/2020/11/8-create-public-client-300x214.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/8-create-public-client-1024x729.png 1024w, https://blog.codersee.com/wp-content/uploads/2020/11/8-create-public-client-768x547.png 768w" sizes="auto, (max-width: 1065px) 100vw, 1065px" /></p>
<p>This time, only the<strong> Direct Access Grants flow</strong> is <strong>enabled</strong> and the Access Type is set to <strong>public</strong>.<br />
The <strong>public</strong> access type is used for clients, which cannot keep secrets securely, like frontend applications for instance.</p>
<h3 class="article-heading-sub">3.4. Create Users</h3>
<p>As the last step for Keycloak configuration, we will create users. Let&#8217;s head to the <em><strong>Users</strong></em> page, and create a user names <em><strong>example_user</strong></em>:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1637 size-full" title="Image shows how to craete a new user in Keycloak" src="http://blog.codersee.com/wp-content/uploads/2020/11/9-add-user-form.png" alt="Image shows how to craete a new user in Keycloak" width="1049" height="543" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/9-add-user-form.png 1049w, https://blog.codersee.com/wp-content/uploads/2020/11/9-add-user-form-300x155.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/9-add-user-form-1024x530.png 1024w, https://blog.codersee.com/wp-content/uploads/2020/11/9-add-user-form-768x398.png 768w" sizes="auto, (max-width: 1049px) 100vw, 1049px" /></p>
<p>Let&#8217;s mark him <strong>enabled</strong> and his email <strong>verified </strong>as well.</p>
<p>The next important thing we need to do is to <strong>set the password</strong> for the user in the <em><strong>Credentials</strong></em> tab:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1638 size-full" title="Image shows how to set the password for user in Keycloak" src="http://blog.codersee.com/wp-content/uploads/2020/11/10-set-password-for-user.png" alt="Image shows how to set the password for user in Keycloak" width="820" height="482" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/10-set-password-for-user.png 820w, https://blog.codersee.com/wp-content/uploads/2020/11/10-set-password-for-user-300x176.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/10-set-password-for-user-768x451.png 768w" sizes="auto, (max-width: 820px) 100vw, 820px" /></p>
<p>And as the last step, let&#8217;s add the desired role to the user. Let&#8217;s select the <strong>ROLE_USER</strong> and hit the<em><strong> Add selected</strong></em> button, just like below:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1639 size-full" title="Image shows how to add the role to the user in Keycloak" src="http://blog.codersee.com/wp-content/uploads/2020/11/11-Add-role-to-user.png" alt="Image shows how to add the role to the user in Keycloak" width="890" height="424" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/11-Add-role-to-user.png 890w, https://blog.codersee.com/wp-content/uploads/2020/11/11-Add-role-to-user-300x143.png 300w, https://blog.codersee.com/wp-content/uploads/2020/11/11-Add-role-to-user-768x366.png 768w" sizes="auto, (max-width: 890px) 100vw, 890px" /></p>
<p>Please repeat the same steps for the <strong>admin user</strong>. The only difference will be the selection of the <strong>ROLE_ADMIN</strong> instead.</p>
<h2 class="article-heading-main">4. Configure Spring Boot App</h2>
<h3 class="article-heading-sub">4.1. Imports</h3>
<p>As usual, we will start the configuration of our Spring Boot application with imports. Let&#8217;s head to the <em><strong>build.gradle.kts</strong></em> file and add the following dependencies:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="groovy">implementation("org.springframework.boot:spring-boot-starter-web")
implementation("org.keycloak:keycloak-spring-boot-starter:11.0.3")
implementation("org.springframework.boot:spring-boot-starter-security:2.4.0")
</pre>
<h3 class="article-heading-sub">4.2. Configure Application Properties</h3>
<p>As the next step, let&#8217;s open the <em><strong>/resources/application.yaml</strong></em> file and paste the following config:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="yaml">keycloak:
  realm: example
  resource: spring-boot
  bearer-only: true
  auth-server-url: http://localhost:8090/auth
</pre>
<p>Let&#8217;s see what each of the above configs mean:</p>
<ul>
<li><strong>realm</strong>&#8211; as the name suggests, it is the name of the created realm,</li>
<li><strong>resource</strong>&#8211; matches the name of the created client,</li>
<li><strong>bearer-only</strong>&#8211; this flag indicates, that our service has been created as a bearer-only, by default it is set to false,</li>
<li><strong>auth-server-url</strong>&#8211; it is the URL of our Keycloak server</li>
</ul>
<h3 class="article-heading-sub">4.3. Configure Spring Security</h3>
<p>As the next step, let&#8217;s create the <strong>WebSecurityConfig</strong> class:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig : KeycloakWebSecurityConfigurerAdapter() {

    @Autowired
    fun configureGlobal(auth: AuthenticationManagerBuilder) {
        val keycloakAuthenticationProvider = keycloakAuthenticationProvider()
        keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
        auth.authenticationProvider(keycloakAuthenticationProvider)
    }

    @Bean
    override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
        return NullAuthenticatedSessionStrategy()
    }

    @Bean
    fun keycloakConfigResolver(): KeycloakConfigResolver {
        return KeycloakSpringBootConfigResolver()
    }

    override fun configure(http: HttpSecurity) {
        super.configure(http)
        http
            .authorizeRequests()
            .antMatchers("/api/public/**").permitAll()
            .anyRequest().fullyAuthenticated()
    }
}
</pre>
<p>I totally understand that this config might be a little overwhelming, so let&#8217;s just break it down:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@KeycloakConfiguration
@EnableGlobalMethodSecurity(prePostEnabled = true)
class WebSecurityConfig : KeycloakWebSecurityConfigurerAdapter()
</pre>
<p><em><strong>@KeycloakConfiguration</strong></em> annotation provides a Keycloak based Spring security configuration. It is used with classes extending <strong>KeycloakWebSecurityConfigurerAdapter</strong>, just like in our case. The <em><strong>@EnableGlobalMethodSecurity</strong></em> with <em><strong>prePostEnabled </strong></em>set to true enables the Spring Security global method security and <em><strong>@PreAuthorize</strong></em> annotations.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Autowired
fun configureGlobal(auth: AuthenticationManagerBuilder) {
    val keycloakAuthenticationProvider = keycloakAuthenticationProvider()
    keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(SimpleAuthorityMapper())
    auth.authenticationProvider(keycloakAuthenticationProvider)
}</pre>
<p>The <em><strong>configureGlobal</strong></em> function takes the <strong>AuthenticationManagerBuilder</strong> bean as a parameter and sets the <strong>KeycloakAuthenticationProvider</strong> as the authentication provider. Additionally, it sets the <strong>SimpleAuthorityMapper</strong> as the authority mapper.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@Bean
override fun sessionAuthenticationStrategy(): SessionAuthenticationStrategy {
    return NullAuthenticatedSessionStrategy()
}

@Bean
fun keycloakConfigResolver(): KeycloakConfigResolver {
    return KeycloakSpringBootConfigResolver()
}</pre>
<p>With these two functions, we declare the <strong>KeycloakSpringBootConfigResolver</strong> bean and that we do not want the session to be created, as well.</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">override fun configure(http: HttpSecurity) {
    super.configure(http)
    http
        .authorizeRequests()
        .antMatchers("/api/public/**").permitAll()
        .anyRequest().fullyAuthenticated()
}</pre>
<p>Finally, we override the configure method derived from the <strong>KeycloakWebSecurityConfigurerAdapter</strong>. With this config, all requests except the <em><strong>/api/public/**</strong></em> need to be fully authenticated.</p>
<h2 class="article-heading-main">5. Create Rest Controller</h2>
<p>With all the above being done, we can create an <strong>ExampleController</strong> class, which consists of three endpoints:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="kotlin">@RestController
@RequestMapping("/api")
class ExampleController {

    @GetMapping("/example")
    @PreAuthorize("hasRole('USER')")
    fun getUserInfo(): ResponseEntity&lt;String&gt; =
        ResponseEntity.ok("User info")

    @GetMapping("/example/admin")
    @PreAuthorize("hasRole('ADMIN')")
    fun getAdminInfo(): ResponseEntity&lt;String&gt; =
        ResponseEntity.ok("Admin info")

    @GetMapping("/public/example")
    fun getPublicInfo(): ResponseEntity&lt;String&gt; =
        ResponseEntity.ok("Public info")
}
</pre>
<p>The first two of them require the user to either have the <strong>ROLE_USER</strong> or <strong>ROLE_ADMIN</strong> assigned to him. Nevertheless, the last endpoint permits anyone, even without the token.</p>
<h2 class="article-heading-main">6. Testing with cURL</h2>
<p>Finally, we can validate if everything is working as expected.</p>
<p>Let&#8217;s start by obtaining the access token:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">curl --location --request POST 'http://localhost:8090/auth/realms/example/protocol/openid-connect/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'username=some_user' \
--data-urlencode 'password=password' \
--data-urlencode 'grant_type=password' \
--data-urlencode 'client_id=public'
</pre>
<p>As the result we should get the following JSON object:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">{
    "access_token":  [TOKEN],
    "expires_in": 300,
    "refresh_expires_in": 1800,
    "refresh_token": [TOKEN],
    "token_type": "bearer",
    "not-before-policy": 0,
    "session_state": "7981f2e1-4e50-44d6-ac81-2ba508a59680",
    "scope": "email profile"
}
</pre>
<p>The most important part of the response for us is the <strong>JWT access token</strong>, which we will use to query our endpoints. If you would like to see, how many informations are encoded within it, I highly recommend to visit <a href="http://jwt.io/" target="_blank" rel="noopener noreferrer">jwt.io</a> and check it out.</p>
<p>To fully validate our endpoints, let&#8217;s query all of them with different tokens and see if results <span class="VIiyi" lang="en"><span class="JLqJ4b ChMk0b" data-language-for-alternatives="en" data-language-to-translate-into="pl" data-phrase-index="0">correspond to our assumptions</span></span>:</p>
<pre class="EnlighterJSRAW" data-enlighter-language="raw">curl --location --request GET 'http://localhost:8080/api/example' \
--header 'Authorization: Bearer [TOKEN]' 

## Result for both admin and user:
User info

curl --location --request GET 'http://localhost:8080/api/example/admin' \
--header 'Authorization: Bearer [TOKEN]' 

## Result only for admin:
Admin info

curl --location --request GET 'http://localhost:8080/api/public/example' 

## Result for any request:
Public info
</pre>
<h2 class="article-heading-main">7. Export Realm Configuration</h2>
<p>In the beginning, I&#8217;ve promised to show you how to export the realm configuration. The only thing we need to do is to click the <strong><em>Export</em></strong> tab on the left sidebar and nextly the <em><strong>Export</strong></em> button on the next page:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1641 size-full" title="Image shows how to export the Keycloak configuration to a JSON file." src="http://blog.codersee.com/wp-content/uploads/2020/11/12-export-configuration.png" alt="Image shows how to export the Keycloak configuration to a JSON file." width="390" height="200" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/12-export-configuration.png 390w, https://blog.codersee.com/wp-content/uploads/2020/11/12-export-configuration-300x154.png 300w" sizes="auto, (max-width: 390px) 100vw, 390px" /></p>
<p>As a result, a <a href="https://github.com/codersee-blog/kotlin-spring-boot-keycloak-intro/blob/master/src/main/resources/realm-export.json" target="_blank" rel="noopener noreferrer">JSON file</a> will be generated.</p>
<p>If we would like to import the configuration, all we need is to head to the <em><strong>Import</strong> </em>section:</p>
<p><img loading="lazy" decoding="async" class="alignnone wp-image-1642 size-full" title="Image shows how to import the realm config from a JSON file" src="http://blog.codersee.com/wp-content/uploads/2020/11/13-import-config.png" alt="Image shows how to import the realm config from a JSON file" width="585" height="364" srcset="https://blog.codersee.com/wp-content/uploads/2020/11/13-import-config.png 585w, https://blog.codersee.com/wp-content/uploads/2020/11/13-import-config-300x187.png 300w" sizes="auto, (max-width: 585px) 100vw, 585px" /></p>
<h2 class="article-heading-main">8. Conclusion</h2>
<p>And that would be all for this article. We&#8217;ve covered step by step, how to set up the <strong>Keycloak server with Spring Boot and Kotlin</strong>. Moreover, we&#8217;ve had a chance to see how to use it to secure <strong>Spring Boot REST endpoints with</strong> <em><strong>@PreAuthorize</strong></em>.</p>
<p>As I have written in the beginning, this is the first article in a series. If you would like me to cover some special aspects of the <strong>Keycloak/Spring Boot integration</strong>, please let me know in the comments, or by the <strong><a href="https://codersee.com/contact/" target="_blank" rel="noopener noreferrer">contact</a> </strong>form.</p>
<p>Also, to see the fully working project, please refer to this <a href="https://github.com/codersee-blog/kotlin-spring-boot-keycloak-intro" target="_blank" rel="noopener noreferrer">GitHub repository</a>.</p>
<p>The post <a href="https://blog.codersee.com/keycloak-with-spring-boot-and-kotlin-introduction/">Keycloak with Spring Boot and Kotlin- Introduction</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/keycloak-with-spring-boot-and-kotlin-introduction/feed/</wfw:commentRss>
			<slash:comments>3</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-02-25 18:19:26 by W3 Total Cache
-->