CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/maven-io-ktor--ktor-server-auth-jvm

Authentication and authorization plugin for Ktor server applications

Pending
Overview
Eval results
Files

bearer-auth.mddocs/

Bearer Authentication

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.

Configuration

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)
}

Configuration Properties

  • realm: Optional authentication realm for challenge responses
  • authHeader: Custom function to extract auth header from request

Configuration Functions

  • authenticate: Suspend function that validates bearer token and returns a principal
  • challenge: Custom challenge function for authentication failures
  • skipWhen: Predicate to skip authentication for certain calls
  • authSchemes: Configure supported authentication schemes (default: "Bearer")

Basic Usage

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}")
        }
    }
}

JWT Token Validation

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
        }
    }
}

API Key Authentication

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
        }
    }
}

Custom Header Extraction

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)
    }
}

API Reference

Provider Class

class BearerAuthenticationProvider internal constructor(
    config: Config
) : AuthenticationProvider(config)

Credential Type

data class BearerTokenCredential(val token: String)

Authentication Function Type

typealias AuthenticationFunction<C> = suspend ApplicationCall.(C) -> Any?

Header Extraction Function Type

typealias AuthHeaderFunction = (ApplicationCall) -> HttpAuthHeader?

Advanced Configuration

Multi-Token Support

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
        }
    }
}

Token Refresh Integration

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
        }
    }
}

Custom Challenge Response

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)
    }
}

Integration Patterns

Database Token Validation

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
                    }
                }
        }
    }
}

OAuth 2.0 Access Token Validation

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
        }
    }
}

Error Handling

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:

  • 401 Unauthorized: When token is missing, invalid, or expired
  • 403 Forbidden: When token is valid but lacks required permissions

Security Best Practices

  • Use secure token generation with sufficient entropy
  • Implement token expiration and refresh mechanisms
  • Store tokens securely (avoid local storage for sensitive tokens)
  • Use HTTPS to protect tokens in transit
  • Implement proper token revocation
  • Consider using short-lived access tokens with refresh tokens
  • Validate token format and signature before processing
  • Log authentication failures for security monitoring

Install with Tessl CLI

npx tessl i tessl/maven-io-ktor--ktor-server-auth-jvm

docs

basic-auth.md

bearer-auth.md

form-session-auth.md

index.md

jvm-features.md

oauth.md

tile.json