Ktor 3.4.0 Help

API Key authentication

API Key authentication is a simple authentication method where clients pass a secret key as part of their request, typically in a header. This key serves as both the identifier and the authentication mechanism.

Ktor allows you to use API Key authentication for securing routes and validating client requests.

Add dependencies

To enable API Key authentication, add the ktor-server-auth and ktor-server-auth-api-key artifacts in the build script:

implementation("io.ktor:ktor-server-auth-api-key:$ktor_version") implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation "io.ktor:ktor-server-auth-api-key:$ktor_version" implementation "io.ktor:ktor-server-auth:$ktor_version"
<dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-auth-api-key-jvm</artifactId> <version>${ktor_version}</version> </dependency> <dependency> <groupId>io.ktor</groupId> <artifactId>ktor-server-auth</artifactId> <version>${ktor_version}</version> </dependency>

API Key authentication flow

The API Key authentication flow looks as follows:

  1. A client makes a request with an API key included in a header (typically X-API-Key) to a specific route in a server application.

  2. The server validates the API key using custom validation logic.

  3. If the key is valid, the server responds with the requested content. If the key is invalid or missing, the server responds with a 401 Unauthorized status.

Install API Key authentication

To install the apiKey authentication provider, call the apiKey function inside the install(Authentication) block:

import io.ktor.server.application.* import io.ktor.server.auth.* // ... install(Authentication) { apiKey { // Configure API Key authentication } }

You can optionally specify a provider name that can be used to authenticate a specified route.

Configure API Key authentication

In this section, we'll see the configuration specifics of the apiKey authentication provider.

Step 1: Configure an API Key provider

The apiKey authentication provider exposes its settings via the ApiKeyAuthenticationProvider.Config class. In the example below, the following settings are specified:

  • The validate function receives the API key extracted from the request and returns a Principal in the case of successful authentication or null if authentication fails.

Here's a minimal example:

data class AppPrincipal(val key: String) : Principal install(Authentication) { apiKey { validate { keyFromHeader -> val expectedApiKey = "this-is-expected-key" keyFromHeader .takeIf { it == expectedApiKey } ?.let { AppPrincipal(it) } } } }

Customize key location

By default, the apiKey provider looks for the API key in the X-API-Key header.

You can use headerName to specify a custom header:

apiKey("api-key-header") { headerName = "X-Secret-Key" validate { key -> // ... } }

Step 2: Validate API keys

The validation logic depends on your application's requirements. Here are common approaches:

Static key comparison

For simple cases, you can compare against a predefined key:

apiKey { validate { keyFromHeader -> val expectedApiKey = environment.config.property("api.key").getString() keyFromHeader .takeIf { it == expectedApiKey } ?.let { AppPrincipal(it) } } }

Database lookup

For multiple API keys, validate against a database:

apiKey { validate { keyFromHeader -> // Looks up the key in the database val user = database.findUserByApiKey(keyFromHeader) user?.let { UserIdPrincipal(it.username) } } }

Multiple validation criteria

You can implement complex validation logic:

apiKey { validate { keyFromHeader -> val apiKey = database.findApiKey(keyFromHeader) // Checks if the key exists, is active, and not expired if (apiKey != null && apiKey.isActive && apiKey.expiresAt > Clock.System.now() ) { UserIdPrincipal(apiKey.userId) } else { null } } }

Step 3: Configure challenge

You can customize the response sent when authentication fails using the challenge function:

apiKey { validate { key -> // Validation logic } challenge { defaultScheme, realm -> call.respond( HttpStatusCode.Unauthorized, "Invalid or missing API key" ) } }

Step 4: Protect specific resources

After configuring the apiKey provider, you can protect specific resources in your application using the authenticate function. In the case of successful authentication, you can retrieve an authenticated principal inside a route handler using the call.principal function.

routing { authenticate { get("/") { val principal = call.principal<AppPrincipal>()!! call.respondText("Hello, authenticated client! Your key: ${principal.key}") } } }

API Key authentication example

Here's a complete minimal example of API Key authentication:

import io.ktor.server.application.* import io.ktor.server.auth.* import io.ktor.server.response.* import io.ktor.server.routing.* data class AppPrincipal(val key: String) : Principal fun Application.module() { val expectedApiKey = "this-is-expected-key" install(Authentication) { apiKey { validate { keyFromHeader -> keyFromHeader .takeIf { it == expectedApiKey } ?.let { AppPrincipal(it) } } } } routing { authenticate { get("/") { val principal = call.principal<AppPrincipal>()!! call.respondText("Key: ${principal.key}") } } } }

Best practices

When implementing API Key authentication, consider the following best practices:

  1. Use HTTPS: Always transmit API keys over HTTPS to prevent interception.

  2. Store securely: Never hardcode API keys in source code. Use environment variables or secure configuration management.

  3. Key rotation: Implement a mechanism for rotating API keys periodically.

  4. Rate limiting: Combine API Key authentication with rate limiting to prevent abuse.

  5. Logging: Log authentication failures for security monitoring, but never log the actual API keys.

  6. Key format: Use cryptographically secure random strings for API keys (for example, UUID or base64-encoded random bytes).

  7. Multiple keys: Consider supporting multiple API keys per user for different applications or purposes.

  8. Expiration: Implement key expiration for enhanced security.

19 December 2025