0
# Form and Session Authentication
1
2
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.
3
4
## Form Authentication
5
6
HTML form-based authentication that processes username and password from POST form data.
7
8
### Configuration
9
10
```kotlin { .api }
11
fun AuthenticationConfig.form(
12
name: String? = null,
13
configure: FormAuthenticationProvider.Config.() -> Unit
14
)
15
16
@KtorDsl
17
class FormAuthenticationProvider.Config(name: String?) : AuthenticationProvider.Config(name) {
18
var userParamName: String
19
var passwordParamName: String
20
fun validate(body: suspend ApplicationCall.(UserPasswordCredential) -> Any?)
21
fun challenge(body: FormAuthChallengeFunction)
22
fun challenge(redirectUrl: String)
23
fun challenge(redirect: Url)
24
fun skipWhen(predicate: ApplicationCallPredicate)
25
}
26
```
27
28
### Configuration Properties
29
30
- **`userParamName`**: HTML form parameter name for username (default: "user")
31
- **`passwordParamName`**: HTML form parameter name for password (default: "password")
32
33
### Configuration Functions
34
35
- **`validate`**: Suspend function that validates form credentials
36
- **`challenge`**: Function to handle authentication failures (typically redirects to login page)
37
- **`challenge(redirectUrl: String)`**: Convenience function to redirect to URL on failure
38
- **`challenge(redirect: Url)`**: Convenience function to redirect to Url on failure
39
- **`skipWhen`**: Predicate to skip authentication for certain calls
40
41
### Basic Form Authentication
42
43
```kotlin
44
install(Authentication) {
45
form("auth-form") {
46
userParamName = "username"
47
passwordParamName = "password"
48
validate { credentials ->
49
if (credentials.name == "admin" && credentials.password == "secret") {
50
UserIdPrincipal(credentials.name)
51
} else {
52
null
53
}
54
}
55
challenge {
56
call.respondRedirect("/login?error=true")
57
}
58
}
59
}
60
61
routing {
62
authenticate("auth-form") {
63
post("/secure-form") {
64
val principal = call.principal<UserIdPrincipal>()
65
call.respond("Form submitted by ${principal?.name}")
66
}
67
}
68
}
69
```
70
71
## Session Authentication
72
73
Session-based authentication that maintains user sessions using Ktor's session management.
74
75
### Configuration
76
77
```kotlin { .api }
78
fun <T : Any> AuthenticationConfig.session(
79
name: String? = null,
80
configure: SessionAuthenticationProvider.Config<T>.() -> Unit
81
)
82
83
@KtorDsl
84
class SessionAuthenticationProvider.Config<T : Any>(name: String?) : AuthenticationProvider.Config(name) {
85
fun validate(body: suspend ApplicationCall.(T) -> Any?)
86
fun challenge(body: SessionAuthChallengeFunction<T>)
87
fun challenge(redirectUrl: String)
88
fun challenge(redirect: Url)
89
fun skipWhen(predicate: ApplicationCallPredicate)
90
}
91
```
92
93
### Configuration Functions
94
95
- **`validate`**: Function that validates session data and returns principal
96
- **`challenge`**: Function to handle session authentication failures
97
- **`challenge(redirectUrl: String)`**: Convenience function to redirect to URL on failure
98
- **`challenge(redirect: Url)`**: Convenience function to redirect to Url on failure
99
- **`skipWhen`**: Predicate to skip authentication for certain calls
100
101
### Basic Session Authentication
102
103
```kotlin
104
// Define session data class
105
@Serializable
106
data class UserSession(val userId: String, val loginTime: Long)
107
108
install(Sessions) {
109
cookie<UserSession>("user_session") {
110
cookie.httpOnly = true
111
cookie.secure = true
112
}
113
}
114
115
install(Authentication) {
116
session<UserSession>("auth-session") {
117
validate { session ->
118
// Validate session is not expired
119
if (Clock.System.now().epochSeconds - session.loginTime < 3600) {
120
UserIdPrincipal(session.userId)
121
} else {
122
null
123
}
124
}
125
challenge {
126
call.respondRedirect("/login")
127
}
128
}
129
}
130
131
routing {
132
authenticate("auth-session") {
133
get("/profile") {
134
val principal = call.principal<UserIdPrincipal>()
135
call.respond("Profile for ${principal?.name}")
136
}
137
}
138
}
139
```
140
141
## Combined Form and Session Authentication
142
143
Typical web application pattern combining form login with session maintenance:
144
145
```kotlin
146
@Serializable
147
data class UserSession(val userId: String, val loginTime: Long)
148
149
install(Sessions) {
150
cookie<UserSession>("user_session") {
151
cookie.httpOnly = true
152
cookie.secure = true
153
cookie.maxAgeInSeconds = 3600
154
}
155
}
156
157
install(Authentication) {
158
// Form authentication for login
159
form("login-form") {
160
userParamName = "username"
161
passwordParamName = "password"
162
validate { credentials ->
163
userService.authenticate(credentials)
164
}
165
challenge {
166
call.respondRedirect("/login?error=invalid")
167
}
168
}
169
170
// Session authentication for protected pages
171
session<UserSession>("user-session") {
172
validate { session ->
173
userService.getUser(session.userId)?.let { user ->
174
UserIdPrincipal(user.id)
175
}
176
}
177
challenge {
178
call.respondRedirect("/login")
179
}
180
}
181
}
182
183
routing {
184
// Login page and form processing
185
get("/login") {
186
call.respondText(loginPageHtml, ContentType.Text.Html)
187
}
188
189
authenticate("login-form") {
190
post("/login") {
191
val principal = call.principal<UserIdPrincipal>()!!
192
call.sessions.set(UserSession(principal.name, Clock.System.now().epochSeconds))
193
call.respondRedirect("/dashboard")
194
}
195
}
196
197
// Protected pages using session auth
198
authenticate("user-session") {
199
get("/dashboard") {
200
val principal = call.principal<UserIdPrincipal>()
201
call.respond("Welcome to dashboard, ${principal?.name}!")
202
}
203
204
post("/logout") {
205
call.sessions.clear<UserSession>()
206
call.respondRedirect("/")
207
}
208
}
209
}
210
```
211
212
## Additional Session DSL Functions
213
214
```kotlin { .api }
215
fun <T : Any> AuthenticationConfig.session(name: String? = null): SessionAuthenticationProvider<T>
216
217
fun <T : Any> AuthenticationConfig.session(
218
name: String?,
219
kClass: KClass<T>
220
): SessionAuthenticationProvider<T>
221
222
fun <T : Any> AuthenticationConfig.session(
223
name: String? = null,
224
configure: SessionAuthenticationProvider.Config<T>.() -> Unit
225
): SessionAuthenticationProvider<T>
226
227
fun <T : Any> AuthenticationConfig.session(
228
name: String?,
229
kClass: KClass<T>,
230
configure: SessionAuthenticationProvider.Config<T>.() -> Unit
231
): SessionAuthenticationProvider<T>
232
```
233
234
## API Reference
235
236
### Form Authentication Types
237
238
```kotlin { .api }
239
class FormAuthenticationProvider internal constructor(
240
config: Config
241
) : AuthenticationProvider(config)
242
243
typealias FormAuthChallengeFunction = suspend PipelineContext<*, ApplicationCall>.(
244
context: FormAuthChallengeContext
245
) -> Unit
246
247
data class FormAuthChallengeContext(val call: ApplicationCall)
248
```
249
250
### Session Authentication Types
251
252
```kotlin { .api }
253
class SessionAuthenticationProvider<T : Any> internal constructor(
254
config: Config<T>
255
) : AuthenticationProvider(config)
256
257
typealias SessionAuthChallengeFunction<T> = suspend PipelineContext<*, ApplicationCall>.(
258
context: SessionChallengeContext
259
) -> Unit
260
261
data class SessionChallengeContext(val call: ApplicationCall)
262
263
val SessionAuthChallengeKey: String
264
```
265
266
## Advanced Patterns
267
268
### Multi-Step Form Authentication
269
270
```kotlin
271
form("multi-step-auth") {
272
validate { credentials ->
273
val step = call.parameters["step"]
274
when (step) {
275
"1" -> {
276
// First step: validate username
277
if (userService.userExists(credentials.name)) {
278
// Store partial authentication state
279
call.sessions.set(PartialAuth(credentials.name))
280
null // Don't complete authentication yet
281
} else null
282
}
283
"2" -> {
284
// Second step: validate password + 2FA
285
val partial = call.sessions.get<PartialAuth>()
286
if (partial?.username == credentials.name) {
287
val twoFactorCode = call.parameters["2fa_code"]
288
if (userService.validateCredentials(credentials) &&
289
twoFactorService.verify(credentials.name, twoFactorCode)) {
290
call.sessions.clear<PartialAuth>()
291
UserIdPrincipal(credentials.name)
292
} else null
293
} else null
294
}
295
else -> null
296
}
297
}
298
}
299
```
300
301
### Session with Role-Based Access
302
303
```kotlin
304
@Serializable
305
data class UserSession(val userId: String, val roles: List<String>)
306
307
session<UserSession>("admin-session") {
308
validate { session ->
309
if ("admin" in session.roles) {
310
UserIdPrincipal(session.userId)
311
} else null
312
}
313
challenge {
314
call.respond(HttpStatusCode.Forbidden, "Admin access required")
315
}
316
}
317
```
318
319
### Custom Session Storage
320
321
```kotlin
322
session<UserSession>("database-session") {
323
validate { session ->
324
// Validate against database instead of just session data
325
transaction {
326
Sessions.select { Sessions.sessionId eq session.sessionId }
327
.singleOrNull()
328
?.let { row ->
329
if (row[Sessions.expiresAt] > Clock.System.now()) {
330
UserIdPrincipal(row[Sessions.userId])
331
} else null
332
}
333
}
334
}
335
}
336
```
337
338
## Security Considerations
339
340
### Form Authentication
341
- Always use HTTPS to protect credentials in transit
342
- Implement CSRF protection for form submissions
343
- Use rate limiting to prevent brute force attacks
344
- Validate and sanitize all form inputs
345
- Consider implementing account lockout mechanisms
346
347
### Session Authentication
348
- Use secure session cookies (httpOnly, secure, sameSite)
349
- Implement session timeout and rotation
350
- Store minimal data in sessions, reference detailed data from server
351
- Invalidate sessions on logout and privilege changes
352
- Use secure session storage for sensitive applications
353
- Implement session fixation protection