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

form-session-auth.mddocs/

Form and Session Authentication

Form-based authentication for HTML forms and session-based authentication for maintaining user sessions. These providers are commonly used together in web applications for traditional login/logout workflows.

Form Authentication

HTML form-based authentication that processes username and password from POST form data.

Configuration

fun AuthenticationConfig.form(
    name: String? = null,
    configure: FormAuthenticationProvider.Config.() -> Unit
)

@KtorDsl
class FormAuthenticationProvider.Config(name: String?) : AuthenticationProvider.Config(name) {
    var userParamName: String
    var passwordParamName: String
    fun validate(body: suspend ApplicationCall.(UserPasswordCredential) -> Any?)
    fun challenge(body: FormAuthChallengeFunction)
    fun challenge(redirectUrl: String)
    fun challenge(redirect: Url)
    fun skipWhen(predicate: ApplicationCallPredicate)
}

Configuration Properties

  • userParamName: HTML form parameter name for username (default: "user")
  • passwordParamName: HTML form parameter name for password (default: "password")

Configuration Functions

  • validate: Suspend function that validates form credentials
  • challenge: Function to handle authentication failures (typically redirects to login page)
  • challenge(redirectUrl: String): Convenience function to redirect to URL on failure
  • challenge(redirect: Url): Convenience function to redirect to Url on failure
  • skipWhen: Predicate to skip authentication for certain calls

Basic Form Authentication

install(Authentication) {
    form("auth-form") {
        userParamName = "username"
        passwordParamName = "password"
        validate { credentials ->
            if (credentials.name == "admin" && credentials.password == "secret") {
                UserIdPrincipal(credentials.name)
            } else {
                null
            }
        }
        challenge { 
            call.respondRedirect("/login?error=true")
        }
    }
}

routing {
    authenticate("auth-form") {
        post("/secure-form") {
            val principal = call.principal<UserIdPrincipal>()
            call.respond("Form submitted by ${principal?.name}")
        }
    }
}

Session Authentication

Session-based authentication that maintains user sessions using Ktor's session management.

Configuration

fun <T : Any> AuthenticationConfig.session(
    name: String? = null,
    configure: SessionAuthenticationProvider.Config<T>.() -> Unit
)

@KtorDsl
class SessionAuthenticationProvider.Config<T : Any>(name: String?) : AuthenticationProvider.Config(name) {
    fun validate(body: suspend ApplicationCall.(T) -> Any?)
    fun challenge(body: SessionAuthChallengeFunction<T>)
    fun challenge(redirectUrl: String)
    fun challenge(redirect: Url)
    fun skipWhen(predicate: ApplicationCallPredicate)
}

Configuration Functions

  • validate: Function that validates session data and returns principal
  • challenge: Function to handle session authentication failures
  • challenge(redirectUrl: String): Convenience function to redirect to URL on failure
  • challenge(redirect: Url): Convenience function to redirect to Url on failure
  • skipWhen: Predicate to skip authentication for certain calls

Basic Session Authentication

// Define session data class
@Serializable
data class UserSession(val userId: String, val loginTime: Long)

install(Sessions) {
    cookie<UserSession>("user_session") {
        cookie.httpOnly = true
        cookie.secure = true
    }
}

install(Authentication) {
    session<UserSession>("auth-session") {
        validate { session ->
            // Validate session is not expired
            if (Clock.System.now().epochSeconds - session.loginTime < 3600) {
                UserIdPrincipal(session.userId)
            } else {
                null
            }
        }
        challenge {
            call.respondRedirect("/login")
        }
    }
}

routing {
    authenticate("auth-session") {
        get("/profile") {
            val principal = call.principal<UserIdPrincipal>()
            call.respond("Profile for ${principal?.name}")
        }
    }
}

Combined Form and Session Authentication

Typical web application pattern combining form login with session maintenance:

@Serializable
data class UserSession(val userId: String, val loginTime: Long)

install(Sessions) {
    cookie<UserSession>("user_session") {
        cookie.httpOnly = true
        cookie.secure = true
        cookie.maxAgeInSeconds = 3600
    }
}

install(Authentication) {
    // Form authentication for login
    form("login-form") {
        userParamName = "username"
        passwordParamName = "password"
        validate { credentials ->
            userService.authenticate(credentials)
        }
        challenge {
            call.respondRedirect("/login?error=invalid")
        }
    }
    
    // Session authentication for protected pages
    session<UserSession>("user-session") {
        validate { session ->
            userService.getUser(session.userId)?.let { user ->
                UserIdPrincipal(user.id)
            }
        }
        challenge {
            call.respondRedirect("/login")
        }
    }
}

routing {
    // Login page and form processing
    get("/login") {
        call.respondText(loginPageHtml, ContentType.Text.Html)
    }
    
    authenticate("login-form") {
        post("/login") {
            val principal = call.principal<UserIdPrincipal>()!!
            call.sessions.set(UserSession(principal.name, Clock.System.now().epochSeconds))
            call.respondRedirect("/dashboard")
        }
    }
    
    // Protected pages using session auth
    authenticate("user-session") {
        get("/dashboard") {
            val principal = call.principal<UserIdPrincipal>()
            call.respond("Welcome to dashboard, ${principal?.name}!")
        }
        
        post("/logout") {
            call.sessions.clear<UserSession>()
            call.respondRedirect("/")
        }
    }
}

Additional Session DSL Functions

fun <T : Any> AuthenticationConfig.session(name: String? = null): SessionAuthenticationProvider<T>

fun <T : Any> AuthenticationConfig.session(
    name: String?,
    kClass: KClass<T>
): SessionAuthenticationProvider<T>

fun <T : Any> AuthenticationConfig.session(
    name: String? = null,
    configure: SessionAuthenticationProvider.Config<T>.() -> Unit
): SessionAuthenticationProvider<T>

fun <T : Any> AuthenticationConfig.session(
    name: String?,
    kClass: KClass<T>,
    configure: SessionAuthenticationProvider.Config<T>.() -> Unit
): SessionAuthenticationProvider<T>

API Reference

Form Authentication Types

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

typealias FormAuthChallengeFunction = suspend PipelineContext<*, ApplicationCall>.(
    context: FormAuthChallengeContext
) -> Unit

data class FormAuthChallengeContext(val call: ApplicationCall)

Session Authentication Types

class SessionAuthenticationProvider<T : Any> internal constructor(
    config: Config<T>
) : AuthenticationProvider(config)

typealias SessionAuthChallengeFunction<T> = suspend PipelineContext<*, ApplicationCall>.(
    context: SessionChallengeContext
) -> Unit

data class SessionChallengeContext(val call: ApplicationCall)

val SessionAuthChallengeKey: String

Advanced Patterns

Multi-Step Form Authentication

form("multi-step-auth") {
    validate { credentials ->
        val step = call.parameters["step"]
        when (step) {
            "1" -> {
                // First step: validate username
                if (userService.userExists(credentials.name)) {
                    // Store partial authentication state
                    call.sessions.set(PartialAuth(credentials.name))
                    null // Don't complete authentication yet
                } else null
            }
            "2" -> {
                // Second step: validate password + 2FA
                val partial = call.sessions.get<PartialAuth>()
                if (partial?.username == credentials.name) {
                    val twoFactorCode = call.parameters["2fa_code"]
                    if (userService.validateCredentials(credentials) && 
                        twoFactorService.verify(credentials.name, twoFactorCode)) {
                        call.sessions.clear<PartialAuth>()
                        UserIdPrincipal(credentials.name)
                    } else null
                } else null
            }
            else -> null
        }
    }
}

Session with Role-Based Access

@Serializable
data class UserSession(val userId: String, val roles: List<String>)

session<UserSession>("admin-session") {
    validate { session ->
        if ("admin" in session.roles) {
            UserIdPrincipal(session.userId)
        } else null
    }
    challenge {
        call.respond(HttpStatusCode.Forbidden, "Admin access required")
    }
}

Custom Session Storage

session<UserSession>("database-session") {
    validate { session ->
        // Validate against database instead of just session data
        transaction {
            Sessions.select { Sessions.sessionId eq session.sessionId }
                .singleOrNull()
                ?.let { row ->
                    if (row[Sessions.expiresAt] > Clock.System.now()) {
                        UserIdPrincipal(row[Sessions.userId])
                    } else null
                }
        }
    }
}

Security Considerations

Form Authentication

  • Always use HTTPS to protect credentials in transit
  • Implement CSRF protection for form submissions
  • Use rate limiting to prevent brute force attacks
  • Validate and sanitize all form inputs
  • Consider implementing account lockout mechanisms

Session Authentication

  • Use secure session cookies (httpOnly, secure, sameSite)
  • Implement session timeout and rotation
  • Store minimal data in sessions, reference detailed data from server
  • Invalidate sessions on logout and privilege changes
  • Use secure session storage for sensitive applications
  • Implement session fixation protection

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