Kotlin
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Null Safety
-
Section 4: Object-Oriented Kotlin
-
Section 5: Functional Kotlin
-
Section 6: Coroutines
-
Section 7: Collections Deep Dive
-
Section 8: Type System Deep Dive
-
Section 9: Interop and Tooling
-
Section 10: Kotlin DSLs and Patterns
-
Section 11: Testing and Quality
-
Section 12: Server-Side Kotlin
-
Section 13: Practical Projects
-
Section 14: Interview Practice
-
Section 15: More Practice Exercises
-
Section 16: More Standard Library
-
Section 17: Multiplatform Kotlin
-
Section 18: kotlin.collections In Depth
-
Section 19: kotlin.text In Depth
-
Section 20: kotlin.ranges and kotlin.sequences
-
Section 21: kotlin.io and File Handling
-
Section 22: kotlinx.coroutines Deep Dive
-
Section 23: kotlin.reflect
-
Section 24: Android Development with Kotlin Overview
-
Section 25: Kotlin Multiplatform Deep Dive
-
Section 26: Kotlin for Backend Deep Dive
-
Section 27: Kotlin Design Patterns
-
Section 28: Advanced Language Features
-
Section 29: More Practice Exercises
-
Section 30: More Interview Practice
-
Section 31: Kotlin Type System Deep Dive
-
Section 32: Kotlin Null Safety Advanced
-
Section 33: Kotlin Testing Deep Dive
-
Section 34: Kotlin Build Tooling Deep Dive
-
Section 35: Kotlin Serialization
-
Section 36: Kotlin Performance Considerations
-
Section 37: Kotlin Native Overview
-
Section 38: Kotlin for Data and Scripting
-
Section 39: More Coroutines Practice
-
Section 40: More Android-Adjacent Patterns
-
Section 41: More Practical Projects
-
Section 42: More Design and Architecture Practice
-
Section 43: Kotlin Language Evolution
-
Section 44: More Interview and Review
-
Section 45: Kotlin Delegation Patterns Deep Dive
-
Section 46: Kotlin Annotations Deep Dive
-
Section 47: Kotlin for Gradle Plugin Development
-
Section 48: Kotlin Concurrency Beyond Coroutines
-
Section 49: Kotlin Compiler Internals
-
Section 50: Real-World Kotlin Case Studies
-
Section 51: Final Practice Projects
-
Section 52: Kotlin for Server-Side Reactive Programming
-
Section 53: More Practice and Drills
-
Section 54: Kotlin Security Practices
118: Ktor Authentication Plugins
I've seen a lot of developers start their Ktor projects by treating security as a routing concern rather than a pipeline concern. When you're first building out a protected resource—say, a "Secure Document Vault" where users can only access their private PDFs—the instinct is to just check for a token right at the top of the route handler. It seems simple enough: grab the header, verify it, and if it's missing, throw a 401.
The Manual Gatekeeper Approach
In a naive implementation, your code ends up looking like this. You're essentially hand-rolling your own security check inside every single protected endpoint:
routing {
get("/vault/documents/{id}") {
val authHeader = call.request.headers["Authorization"]
if (authHeader == null || !isValidToken(authHeader)) {
call.respond(HttpStatusCode.Unauthorized, "Invalid Token")
return@get
}
val docId = call.parameters["id"]
call.respondText("Fetching document $docId")
}
get("/vault/settings") {
val authHeader = call.request.headers["Authorization"]
if (authHeader == null || !isValidToken(authHeader)) {
call.respond(HttpStatusCode.Unauthorized, "Invalid Token")
return@get
}
call.respondText("Vault settings page")
}
}
This works for two routes. Maybe even five. But as a software engineer, you can already see where this falls apart. First, you're violating DRY (Don't Repeat Yourself) in a way that's actually dangerous. If you decide to change your token format or add a second way to authenticate, you have to hunt down every single route and update the logic. More importantly, it only takes one tired developer forgetting to paste that if block into a new route to create a massive security hole in your application. I've been on the receiving end of those kinds of bugs, and they're never fun to explain to a client.
Delegating to the Authentication Plugin
The better way is to move the identity logic out of the business logic and into the Ktor pipeline using the Authentication plugin. Instead of checking headers manually, you define a "provider" that handles the heavy lifting of verifying who the user is, and then you simply wrap your routes in an authenticate block.
Here is how I would actually structure this for a production-ready vault API:
install(Authentication) {
jwt("auth-jwt") {
realm = "Access to Document Vault"
verifier(myJwtVerifier)
validate { credential ->
if (credential.payload.getClaim("username").asString() != "") {
JWTPrincipal(credential.payload)
} else {
null
}
}
}
}
routing {
authenticate("auth-jwt") {
// Everything inside this block is now protected
get("/vault/documents/{id}") {
val principal = call.principal<JWTPrincipal>()
val username = principal?.payload?.getClaim("username")?.asString()
call.respondText("Hello $username, here is document ${call.parameters["id"]}")
}
get("/vault/settings") {
call.respondText("Vault settings page")
}
}
// Public routes stay outside the block
get("/vault/health") {
call.respondText("Vault is online")
}
}
Trading Setup Time for Structural Integrity
The trade-off here is that the plugin approach requires a bit more boilerplate upfront. You have to define the provider, configure the verifier, and understand the concept of a Principal (which is essentially just a Ktor-friendly way of saying "the identity of the current user").
However, the gain in maintainability is huge. By using the authenticate {} wrapper, you've created a visual and logical boundary. You can see at a glance which parts of your API are public and which are private. If you need to add a second authentication method—like an API Key for third-party integrations—you don't have to rewrite your routes; you just add a second provider to the install block and list both in the authenticate call. It turns security from a repetitive chore into a configurable piece of infrastructure.
📋 Practical Task
Exercise: Implementing a Multi-Provider Guard for the Document Vault
You are tasked with upgrading the Document Vault API. Currently, it only supports JWT tokens for users. However, the company now needs to allow "Admin Service" accounts to access the vault using a simple static API Key found in the X-API-KEY header.
Your requirements:
- Install the
Authenticationplugin. - Create two providers: one named
"jwt-auth"and one named"api-key-auth". - For the
"api-key-auth"provider, implement a custom verification logic that checks if theX-API-KEYheader equals"secret-admin-key-123". - Wrap the
/vault/documents/{id}and/vault/settingsroutes so that they are accessible if either the JWT is valid OR the API Key is valid. - Ensure the
/vault/healthroute remains publicly accessible.
There are no comments for now.