0
# JVM-Specific Authentication Features
1
2
JVM-only authentication features including Digest authentication and comprehensive OAuth 1.0a support. These features require JVM-specific cryptographic operations and are not available on other platforms.
3
4
## Digest Authentication
5
6
HTTP Digest authentication implementation that provides more secure username/password authentication than Basic auth by avoiding transmission of plaintext passwords.
7
8
### Configuration
9
10
```kotlin { .api }
11
fun AuthenticationConfig.digest(
12
name: String? = null,
13
configure: DigestAuthenticationProvider.Config.() -> Unit
14
)
15
16
@KtorDsl
17
class DigestAuthenticationProvider.Config(name: String?) : AuthenticationProvider.Config(name) {
18
var realm: String
19
var algorithmName: String
20
var nonceManager: NonceManager
21
fun digestProvider(digest: DigestProviderFunction)
22
fun validate(body: suspend ApplicationCall.(DigestCredential) -> Any?)
23
fun skipWhen(predicate: ApplicationCallPredicate)
24
}
25
```
26
27
### Configuration Properties
28
29
- **`realm`**: Authentication realm for WWW-Authenticate header
30
- **`algorithmName`**: Message digest algorithm (typically "MD5")
31
- **`nonceManager`**: Manages nonce generation and validation
32
33
### Configuration Functions
34
35
- **`digestProvider`**: Function that provides password digest for username/realm
36
- **`validate`**: Validates digest credentials and returns principal
37
- **`skipWhen`**: Predicate to skip authentication for certain calls
38
39
### Basic Digest Authentication
40
41
```kotlin
42
install(Authentication) {
43
digest("auth-digest") {
44
realm = "Protected Area"
45
algorithmName = "MD5"
46
digestProvider { userName, realm ->
47
// Provide digest for username in realm
48
userService.getPasswordDigest(userName, realm)
49
}
50
validate { credentials ->
51
// Additional validation after digest verification
52
UserIdPrincipal(credentials.userName)
53
}
54
}
55
}
56
57
routing {
58
authenticate("auth-digest") {
59
get("/secure") {
60
val principal = call.principal<UserIdPrincipal>()
61
call.respond("Access granted to ${principal?.name}")
62
}
63
}
64
}
65
```
66
67
### Advanced Digest Configuration
68
69
```kotlin
70
digest("custom-digest") {
71
realm = "Secure API"
72
algorithmName = "SHA-256" // Custom algorithm (client support varies)
73
nonceManager = CustomNonceManager() // Custom nonce management
74
75
digestProvider { userName, realm ->
76
// Calculate HA1 = MD5(username:realm:password)
77
val user = userService.getUser(userName)
78
if (user != null) {
79
MessageDigest.getInstance("MD5").digest(
80
"$userName:$realm:${user.password}".toByteArray()
81
)
82
} else null
83
}
84
85
validate { credentials ->
86
// Additional business logic validation
87
val user = userService.getUser(credentials.userName)
88
if (user?.isActive == true) {
89
UserIdPrincipal(credentials.userName)
90
} else null
91
}
92
}
93
```
94
95
## Digest Authentication API
96
97
### Provider Class
98
99
```kotlin { .api }
100
class DigestAuthenticationProvider internal constructor(
101
config: Config
102
) : AuthenticationProvider(config)
103
```
104
105
### Credential Types
106
107
```kotlin { .api }
108
data class DigestCredential(
109
val realm: String,
110
val userName: String,
111
val digestUri: String,
112
val nonce: String,
113
val opaque: String?,
114
val nonceCount: String?,
115
val algorithm: String?,
116
val response: String,
117
val cnonce: String?,
118
val qop: String?
119
)
120
```
121
122
### Function Types
123
124
```kotlin { .api }
125
typealias DigestProviderFunction = suspend (String, String) -> ByteArray?
126
```
127
128
### Credential Extraction and Verification
129
130
```kotlin { .api }
131
fun ApplicationCall.digestAuthenticationCredentials(): DigestCredential?
132
133
fun HttpAuthHeader.Parameterized.toDigestCredential(): DigestCredential?
134
135
suspend fun DigestCredential.verifier(
136
method: HttpMethod,
137
digester: MessageDigest,
138
userNameRealmPasswordDigest: suspend (String, String) -> ByteArray?
139
): Boolean
140
141
suspend fun DigestCredential.expectedDigest(
142
method: HttpMethod,
143
digester: MessageDigest,
144
userNameRealmPasswordDigest: ByteArray
145
): ByteArray
146
```
147
148
## Nonce Management
149
150
### Nonce Manager Interface
151
152
```kotlin { .api }
153
interface NonceManager {
154
suspend fun newNonce(): String
155
suspend fun verifyNonce(nonce: String): Boolean
156
}
157
158
object GenerateOnlyNonceManager : NonceManager {
159
override suspend fun newNonce(): String
160
override suspend fun verifyNonce(nonce: String): Boolean
161
}
162
```
163
164
### Custom Nonce Manager
165
166
```kotlin
167
class DatabaseNonceManager : NonceManager {
168
override suspend fun newNonce(): String {
169
val nonce = generateSecureNonce()
170
// Store nonce in database with expiration
171
nonceRepository.store(nonce, Clock.System.now() + 5.minutes)
172
return nonce
173
}
174
175
override suspend fun verifyNonce(nonce: String): Boolean {
176
return nonceRepository.isValid(nonce)
177
}
178
}
179
```
180
181
## OAuth 1.0a Support (JVM Only)
182
183
Complete OAuth 1.0a implementation with signature generation and verification.
184
185
### OAuth 1.0a Configuration
186
187
OAuth 1.0a server settings for legacy providers:
188
189
```kotlin { .api }
190
data class OAuth1aServerSettings(
191
val name: String,
192
val requestTokenUrl: String,
193
val authorizeUrl: String,
194
val accessTokenUrl: String,
195
val consumerKey: String,
196
val consumerSecret: String
197
) : OAuthServerSettings()
198
```
199
200
### OAuth 1.0a Flow Implementation
201
202
```kotlin
203
val twitterOAuth1a = OAuthServerSettings.OAuth1aServerSettings(
204
name = "twitter",
205
requestTokenUrl = "https://api.twitter.com/oauth/request_token",
206
authorizeUrl = "https://api.twitter.com/oauth/authorize",
207
accessTokenUrl = "https://api.twitter.com/oauth/access_token",
208
consumerKey = System.getenv("TWITTER_CONSUMER_KEY"),
209
consumerSecret = System.getenv("TWITTER_CONSUMER_SECRET")
210
)
211
212
install(Authentication) {
213
oauth("twitter-oauth1a") {
214
client = HttpClient()
215
providerLookup = { twitterOAuth1a }
216
urlProvider = { url -> redirectUrl(url, "/auth/twitter/callback") }
217
}
218
}
219
220
routing {
221
authenticate("twitter-oauth1a") {
222
get("/auth/twitter") {
223
// Initiates OAuth 1.0a flow
224
}
225
226
get("/auth/twitter/callback") {
227
val principal = call.principal<OAuthAccessTokenResponse.OAuth1a>()
228
if (principal != null) {
229
// Use OAuth 1.0a tokens to make API calls
230
val userProfile = getTwitterProfile(principal.token, principal.tokenSecret)
231
call.sessions.set(UserSession(userProfile.id))
232
call.respondRedirect("/dashboard")
233
} else {
234
call.respondRedirect("/login?error=oauth")
235
}
236
}
237
}
238
}
239
```
240
241
### OAuth 1.0a Token Types
242
243
```kotlin { .api }
244
data class OAuthAccessTokenResponse.OAuth1a(
245
val token: String,
246
val tokenSecret: String,
247
val extraParameters: Parameters = Parameters.Empty
248
) : OAuthAccessTokenResponse()
249
250
data class OAuthCallback.TokenPair(
251
val token: String,
252
val tokenSecret: String
253
) : OAuthCallback()
254
```
255
256
### OAuth 1.0a Exceptions
257
258
```kotlin { .api }
259
sealed class OAuth1aException : Exception() {
260
class MissingTokenException(message: String) : OAuth1aException()
261
}
262
```
263
264
## Full OAuth Procedure Support
265
266
The JVM platform provides complete OAuth implementation including the OAuth procedure class:
267
268
```kotlin { .api }
269
class OAuthProcedure {
270
suspend fun requestToken(): OAuthCallback.TokenPair
271
suspend fun authorizeUrl(requestToken: OAuthCallback.TokenPair): String
272
suspend fun accessToken(authorizedToken: OAuthCallback.TokenPair): OAuthAccessTokenResponse.OAuth1a
273
}
274
```
275
276
## Platform-Specific Cryptographic Features
277
278
### Digest Algorithm Support
279
280
```kotlin
281
digest("sha256-digest") {
282
algorithmName = "SHA-256" // JVM supports multiple algorithms
283
realm = "SHA-256 Protected"
284
digestProvider { userName, realm ->
285
MessageDigest.getInstance("SHA-256").digest(
286
"$userName:$realm:${getPassword(userName)}".toByteArray()
287
)
288
}
289
}
290
```
291
292
### Secure Random Generation
293
294
```kotlin
295
class SecureNonceManager : NonceManager {
296
private val secureRandom = SecureRandom()
297
298
override suspend fun newNonce(): String {
299
val bytes = ByteArray(16)
300
secureRandom.nextBytes(bytes)
301
return bytes.encodeBase64()
302
}
303
}
304
```
305
306
## Integration Examples
307
308
### Digest with Database
309
310
```kotlin
311
digest("database-digest") {
312
realm = "Database Protected"
313
digestProvider { userName, realm ->
314
transaction {
315
Users.select { Users.username eq userName }
316
.singleOrNull()
317
?.let { user ->
318
// Pre-computed digest stored in database
319
user[Users.digestHA1]
320
}
321
}
322
}
323
}
324
```
325
326
### OAuth 1.0a with Custom Signature
327
328
```kotlin
329
class CustomOAuth1aClient(
330
private val consumerKey: String,
331
private val consumerSecret: String
332
) {
333
suspend fun makeSignedRequest(
334
url: String,
335
token: String,
336
tokenSecret: String,
337
parameters: Map<String, String> = emptyMap()
338
): HttpResponse {
339
val oauthParams = generateOAuthParams(url, parameters, token)
340
val signature = generateSignature(url, oauthParams, tokenSecret)
341
342
return httpClient.get(url) {
343
header("Authorization", buildOAuthHeader(oauthParams, signature))
344
parameters.forEach { (key, value) ->
345
parameter(key, value)
346
}
347
}
348
}
349
}
350
```
351
352
## Security Considerations
353
354
### Digest Authentication Security
355
- More secure than Basic auth (no plaintext password transmission)
356
- Vulnerable to rainbow table attacks if using weak hashing
357
- Requires secure nonce management to prevent replay attacks
358
- Client support may be limited compared to Basic/Bearer auth
359
- Use strong digest algorithms when client support allows
360
361
### OAuth 1.0a Security
362
- Requires proper signature generation and verification
363
- More complex than OAuth 2.0 but provides built-in request signing
364
- Protect consumer secrets and token secrets securely
365
- Implement proper timestamp and nonce validation
366
- Handle signature verification failures gracefully