Hi @peter.attardo
Welcome to the Auth0 Community!
I can see that you are having struggles in building a Ktor application with Auth0.
Trying to write a custom servlet-shim is highly discouraged, as Ktor provides a built-in, native OAuth Plugin (io.ktor:ktor-server-auth). This plugin handles OIDC/OAuth2 Authorization Code Grant flows (Universal Login) completely out of the box.
By utilizing Ktor’s native plugin instead of the Java MVC SDK, you can integrate directly with Auth0’s Universal Login endpoints while maintaining native, non-blocking RoutingCall performance.
- : Install Dependencies
Add the native Ktor authentication plugin to your build dependencies (build.gradle.kts):
implementation("io.ktor:ktor-server-auth:$ktor_version")
implementation("io.ktor:ktor-server-sessions:$ktor_version")
- Configure Sessions and Universal Login
In your Ktor application entry point (typically Application.module), install the Sessions and Authentication plugins:
import io.ktor.client.*
import io.ktor.client.engine.cio.*
import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.auth.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import io.ktor.server.sessions.*
data class UserSession(val token: String, val idToken: String) : Principal
fun Application.module() {
install(Sessions) {
cookie<UserSession>("user_session") {
cookie.path = "/"
cookie.maxAgeInSeconds = 3600 // 1 hour
}
}
val httpClient = HttpClient(CIO)
install(Authentication) {
oauth("auth0-oauth") {
urlProvider = { "http://localhost:5173/callback" }
providerLookup = {
OAuthServerSettings.OAuth2ServerSettings(
name = "auth0",
authorizeUrl = "https://{{tenant_domain}}.us.auth0.com/authorize",
accessTokenUrl = "https://{{tenant_domain}}.us.auth0.com/oauth/token",
clientId = "Client_ID",
clientSecret = "YOUR_CLIENT_SECRET",
accessTokenRequiresBasicAuth = false,
requestMethod = HttpMethod.Post,
defaultScopes = listOf("openid", "profile", "email") /
)
}
client = httpClient
}
}
}
- Implement Native
/login, /callback, and /logout Routes
By wrapping routes in the Ktor authenticate block, the platform handles the authorization redirect challenge on /login and validates the incoming state parameter on /callback automatically:
routing {
get("/") {
call.respondText("Welcome to the website!")
}
authenticate("auth0-oauth") {
get("/login") {
}
get("/callback") {
val principal = call.principal<OAuthAccessTokenResponse.OAuth2>()
if (principal != null) {
call.sessions.set(UserSession(
token = principal.accessToken,
idToken = principal.extraParameters["id_token"] ?: ""
))
call.respondRedirect("/dashboard")
} else {
call.respondText("Authentication failed.", status = HttpStatusCode.Unauthorized)
}
}
}
get("/logout") {
call.sessions.clear<UserSession>()
val domain = "tenant_domain.us.auth0.com"
val clientId = "client_id"
val returnTo = "http://localhost:5173"
call.respondRedirect("https://$domain/v2/logout?client_id=$clientId&returnTo=$returnTo")
}
get("/dashboard") {
val session = call.sessions.get<UserSession>()
if (session != null) {
call.respondText("Hello authenticated user! Your Access Token is: ${session.token}")
} else {
call.respondRedirect("/login")
}
}
}
All these resources should be available thorough Ktor’s OAuth Documentation.
Kind Regards,
Nik