---
title: "Add JWT Authentication to Your Ktor API"
description: "Secure your Ktor API using JWT authentication with JWKS."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/kotlin/ktor/jwt-backend"
framework: "Ktor"
language: "Kotlin"
authStrategy: "JWT with JWKS"
pageType: use-case
---

# Add JWT Authentication to Your Ktor API

Secure your Ktor API using JWT authentication with JWKS.

## How Zuplo Handles It

Let Zuplo issue short-lived JWTs signed with a JWKS your Ktor backend can verify — no long-lived API keys touch your origin.

## Ktor Backend Code

```kotlin
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.auth.jwt.*
import io.ktor.features.*
import io.ktor.http.*
import io.ktor.response.*
import io.ktor.routing.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import com.auth0.jwk.JwkProviderBuilder
import java.util.concurrent.TimeUnit


fun main() {
    val issuer = "https://my-api-a32f34.zuplo.api/__zuplo/issuer"

    val jwkProvider = JwkProviderBuilder("$issuer/.well-known/jwks.json")
        .cached(10, 24, TimeUnit.HOURS)
        .rateLimited(10, 1, TimeUnit.MINUTES)
        .build()

    embeddedServer(Netty, port = 8080) {
        install(Authentication) {
            jwt {
                realm = "ktor sample app"
                verifier(jwkProvider, issuer) {
                    acceptLeeway(3)
                    withIssuer(issuer)
                    withAudience("your-audience")
                }
                validate { credential ->
                    if (credential.payload.getClaim("sub").isNull) {
                        null
                    } else {
                        JWTPrincipal(credential.payload)
                    }
                }
                challenge { _, _ ->
                    call.respond(HttpStatusCode.Unauthorized, "Token is not valid or has expired")
                }
            }
        }

        routing {
            authenticate {
                get("/protected") {
                    val principal = call.principal<JWTPrincipal>()
                    val username = principal!!.payload.getClaim("sub").asString()
                    call.respondText("Hello, $username!")
                }
            }

            get("/") {
                call.respondText("Welcome to the public API")
            }
        }
    }.start(wait = true)
}
```

## Example Request

```bash
curl -X GET \
  'https://your-api.zuplo.dev/your-route' \
  -H 'Authorization: Bearer YOUR_API_KEY'
```

## Learn More

- [API Key Authentication on Zuplo](https://zuplo.com/docs/policies/api-key-auth-inbound)
- [JWT Authentication on Zuplo](https://zuplo.com/docs/policies/open-id-jwt-auth-inbound)
- [All use cases](https://zuplo.com/use-cases)
