---
title: "Secure Ktor APIs with API Key Authentication"
description: "Secure your Ktor API using a shared secret."
canonicalUrl: "https://zuplo.com/use-cases/api-key-auth/kotlin/ktor/secure-header"
framework: "Ktor"
language: "Kotlin"
authStrategy: "shared secret header"
pageType: use-case
---

# Secure Ktor APIs with API Key Authentication

Secure your Ktor API using a shared secret.

## How Zuplo Handles It

Put Zuplo in front of your Ktor backend to authenticate API keys and forward a shared secret header so your origin only accepts traffic from Zuplo.

## Ktor Backend Code

```kotlin
import io.ktor.application.*
import io.ktor.response.*
import io.ktor.request.*
import io.ktor.features.*
import io.ktor.routing.*
import io.ktor.http.*
import io.ktor.server.engine.embeddedServer
import io.ktor.server.netty.Netty
import io.ktor.util.AttributeKey
import io.ktor.util.pipeline.PipelineContext
import java.security.MessageDigest
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec

// Environment variable for shared secret
val sharedSecret: String = System.getenv("SHARED_SECRET") ?: throw IllegalStateException("SHARED_SECRET not set")

// Timing-safe comparison
fun timingSafeEqual(a: ByteArray, b: ByteArray): Boolean {
    if (a.size != b.size) return false
    var result = 0
    for (i in a.indices) {
        result = result or (a[i].toInt() xor b[i].toInt())
    }
    return result == 0
}

// Custom feature to validate shared secret
class SharedSecret(val secret: String) {
    class Configuration {
        var secret: String = ""
    }

    companion object Feature : ApplicationFeature<ApplicationCallPipeline, Configuration, SharedSecret> {
        override val key: AttributeKey<SharedSecret> = AttributeKey("SharedSecret")

        override fun install(pipeline: ApplicationCallPipeline, configure: Configuration.() -> Unit): SharedSecret {
            val configuration = Configuration().apply(configure)
            val feature = SharedSecret(configuration.secret)

            pipeline.intercept(ApplicationCallPipeline.Features) {
                val requestSecret = call.request.header("x-shared-secret")
                if (requestSecret == null || !timingSafeEqual(requestSecret.toByteArray(), feature.secret.toByteArray())) {
                    call.respond(HttpStatusCode.Unauthorized, "Invalid secret")
                    finish()
                }
            }
            return feature
        }
    }
}

fun main() {
    embeddedServer(Netty, port = 8080) {
        install(SharedSecret) {
            secret = sharedSecret
        }

        routing {
            get("/protected") {
                call.respondText("Access granted", status = HttpStatusCode.OK)
            }
        }
    }.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)
