Authentication and authorization plugin for Ktor server applications
—
Bearer token authentication for API access tokens, JWT tokens, and other token-based authentication schemes. This provider extracts bearer tokens from the Authorization header and validates them using a custom authentication function.
fun AuthenticationConfig.bearer(
name: String? = null,
configure: BearerAuthenticationProvider.Config.() -> Unit
)
@KtorDsl
class BearerAuthenticationProvider.Config(name: String?) : AuthenticationProvider.Config(name) {
var realm: String?
var authHeader: (ApplicationCall) -> HttpAuthHeader?
fun authenticate(body: suspend ApplicationCall.(BearerTokenCredential) -> Any?)
fun challenge(body: ChallengeFunction)
fun skipWhen(predicate: ApplicationCallPredicate)
fun authSchemes(defaultScheme: String, vararg additionalSchemes: String)
}realm: Optional authentication realm for challenge responsesauthHeader: Custom function to extract auth header from requestauthenticate: Suspend function that validates bearer token and returns a principalchallenge: Custom challenge function for authentication failuresskipWhen: Predicate to skip authentication for certain callsauthSchemes: Configure supported authentication schemes (default: "Bearer")install(Authentication) {
bearer("auth-bearer") {
realm = "Access to the API"
authenticate { tokenCredentials ->
if (tokenCredentials.token == "valid-token") {
UserIdPrincipal("user")
} else {
null
}
}
}
}
routing {
authenticate("auth-bearer") {
get("/api/protected") {
val principal = call.principal<UserIdPrincipal>()
call.respond("Access granted to ${principal?.name}")
}
}
}bearer("jwt-auth") {
realm = "JWT Protected API"
authenticate { tokenCredentials ->
try {
val jwt = JWT.require(Algorithm.HMAC256("secret"))
.build()
.verify(tokenCredentials.token)
UserIdPrincipal(jwt.getClaim("username").asString())
} catch (exception: JWTVerificationException) {
null
}
}
}bearer("api-key-auth") {
realm = "API Key Required"
authenticate { tokenCredentials ->
val apiKey = tokenCredentials.token
if (apiKeyService.isValid(apiKey)) {
val user = apiKeyService.getUser(apiKey)
UserIdPrincipal(user.id)
} else {
null
}
}
}bearer("custom-header") {
authHeader { call ->
// Extract token from custom header
val token = call.request.headers["X-API-Token"]
token?.let { HttpAuthHeader.Single("Bearer", it) }
}
authenticate { tokenCredentials ->
validateCustomToken(tokenCredentials.token)
}
}class BearerAuthenticationProvider internal constructor(
config: Config
) : AuthenticationProvider(config)data class BearerTokenCredential(val token: String)typealias AuthenticationFunction<C> = suspend ApplicationCall.(C) -> Any?typealias AuthHeaderFunction = (ApplicationCall) -> HttpAuthHeader?bearer("multi-token") {
authenticate { tokenCredentials ->
when {
tokenCredentials.token.startsWith("jwt_") ->
validateJWT(tokenCredentials.token.removePrefix("jwt_"))
tokenCredentials.token.startsWith("api_") ->
validateApiKey(tokenCredentials.token.removePrefix("api_"))
else -> null
}
}
}bearer("refreshable-token") {
authenticate { tokenCredentials ->
val token = tokenCredentials.token
when (val result = tokenService.validateToken(token)) {
is TokenResult.Valid -> UserIdPrincipal(result.userId)
is TokenResult.Expired -> {
// Trigger token refresh flow
call.response.headers.append("X-Token-Refresh-Required", "true")
null
}
is TokenResult.Invalid -> null
}
}
}bearer("custom-challenge") {
realm = "Protected API"
challenge { defaultScheme, realm ->
call.respond(
HttpStatusCode.Unauthorized,
mapOf(
"error" -> "invalid_token",
"error_description" -> "The access token is invalid or expired"
)
)
}
authenticate { tokenCredentials ->
validateToken(tokenCredentials.token)
}
}bearer("database-tokens") {
authenticate { tokenCredentials ->
transaction {
AccessTokens.select { AccessTokens.token eq tokenCredentials.token }
.singleOrNull()
?.let { tokenRow ->
if (tokenRow[AccessTokens.expiresAt] > Clock.System.now()) {
UserIdPrincipal(tokenRow[AccessTokens.userId])
} else {
null
}
}
}
}
}bearer("oauth2-token") {
authenticate { tokenCredentials ->
val introspectionResponse = httpClient.post("${oauthServer}/introspect") {
parameter("token", tokenCredentials.token)
basicAuth(clientId, clientSecret)
}.body<IntrospectionResponse>()
if (introspectionResponse.active) {
UserIdPrincipal(introspectionResponse.sub)
} else {
null
}
}
}Bearer authentication uses the standard authentication failure causes:
sealed class AuthenticationFailedCause {
object NoCredentials : AuthenticationFailedCause()
object InvalidCredentials : AuthenticationFailedCause()
class Error(val message: String) : AuthenticationFailedCause()
}Common HTTP responses:
Install with Tessl CLI
npx tessl i tessl/maven-io-ktor--ktor-server-auth-jvm