<?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>RBAC Archives - Codersee blog- Kotlin on the backend</title>
	<atom:link href="https://blog.codersee.com/tag/rbac/feed/" rel="self" type="application/rss+xml" />
	<link></link>
	<description>Kotlin &#38; Backend Tutorials - Learn Through Practice.</description>
	<lastBuildDate>Wed, 16 Apr 2025 04:49:37 +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>RBAC Archives - Codersee blog- Kotlin on the backend</title>
	<link></link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>Secure REST API with Ktor &#8211; Role-based access control (RBAC)</title>
		<link>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/</link>
					<comments>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/#comments</comments>
		
		<dc:creator><![CDATA[Piotr]]></dc:creator>
		<pubDate>Tue, 19 Dec 2023 06:00:00 +0000</pubDate>
				<category><![CDATA[Ktor]]></category>
		<category><![CDATA[Authentication]]></category>
		<category><![CDATA[Authorization]]></category>
		<category><![CDATA[RBAC]]></category>
		<category><![CDATA[REST]]></category>
		<guid isPermaLink="false">https://codersee.com/?p=9008380</guid>

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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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



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

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



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



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



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



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



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



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

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

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



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



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



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

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

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



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



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



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



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



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

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

    refreshTokenRepository.save(refreshToken, username)

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

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

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

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



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



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



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



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



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



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



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



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



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



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



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



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

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

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

  pluginConfig.apply {

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

      val authorized = roles.contains(tokenRole)

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

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



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



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



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



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



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



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



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



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



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

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



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



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



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



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



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

    get {
      val users = userService.findAll()

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

  }
}

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

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

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

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

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



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



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



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



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



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



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



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



<p>As always, you can find a source code in <a href="https://github.com/codersee-blog/kotlin-ktor-custom-authorization-plugin-rbac-jwt/tree/main" target="_blank" rel="noreferrer noopener">this GitHub repository</a> and <strong>let me know your thoughts</strong> in the comments section below 🙂</p>
<p>The post <a href="https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/">Secure REST API with Ktor &#8211; Role-based access control (RBAC)</a> appeared first on <a href="https://blog.codersee.com">Codersee blog- Kotlin on the backend</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.codersee.com/secure-rest-api-ktor-role-based-authorization-rbac/feed/</wfw:commentRss>
			<slash:comments>3</slash:comments>
		
		
			</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 

Served from: blog.codersee.com @ 2026-05-19 18:14:23 by W3 Total Cache
-->