I wanted to support three types of requests in my application:
- Guest access: Requests without an
Authorization
header. - Custom token access: Requests with an
Authorization: Token {token}
header. I need to define a custom mechanism to map the token to an AuthUser. - JWT token access: Requests with an
Authorization: Bearer {jwt}
header.
Each type of request corresponds to a different rate limit:
- Guest access: Lowest rate limit.
- JWT token access: Higher rate limit.
- Custom token access: No rate limit.
What is the best way to implement this setup in Ktor?
I tried using authenticate
to protect specific resources, but I encountered an issue with guest access endpoints. Specifically, when a request includes an Authorization
header, call.authentication.principal()
still returns null
.
Alternatively, I could apply authenticate to all requests. However, I’m not sure how to properly implement this approach.
How can I configure authenticate to handle all request types (guest, custom token, JWT) and apply the appropriate rate limits for each case?
authentication {
jwt {
authHeader { call ->
val header = call.request.headers["Authorization"]
if (header.isNullOrBlank()) {
return@authHeader HttpAuthHeader.Single("NoAuth", "")
}
if (header.startsWith("Bearer ")) {
return@authHeader HttpAuthHeader.Single("Bearer", header.removePrefix("Bearer "))
} else {
return@authHeader HttpAuthHeader.Single("Token", header.removePrefix("Bot "))
}
}
verifier { header ->
val scheme = header.authScheme
return@verifier when (scheme) {
"Bearer" -> Jwt.userVerifier
"Token" -> TODO("")
else -> TODO("")
}
}
validate {
println(it.payload.toString())
return@validate AuthUser(role = AuthUser.Role.NoLogin)
}
challenge { _, _ ->
call.respond(DataVo(401, "Unauthorized", null))
}
}
}
}
// reat limit
install(RateLimit) {
register {
requestKey { call ->
val headers = call.request.headers
headers["X-Real-IP"] ?: headers["X-Forwarded-For"] ?: call.request.origin.remoteHost
}
rateLimiter(limit = 6000, refillPeriod = 1.minutes)
requestWeight { call, _ ->
val user = call.principal<AuthUser>()
when {
// no header Authorization
user == null || !user.isLogin() -> 20
// Authorization: Token
user.isAdmin() -> 0
// Authorization: Bearer
else -> 8
}
}
}
}