webauthn-server-core-jvm¶
Typed JVM ceremony services and store contracts for WebAuthn registration and authentication.
What it provides¶
RegistrationServiceandAuthenticationService- Challenge, credential, and user-account store interfaces
- In-memory store implementations for development and testing
- Ceremony orchestration decoupled from web framework concerns
- Unified authentication start semantics:
AuthenticationStartRequest.userName != null: identified-account flow with populatedallowCredentialsAuthenticationStartRequest.userName == null: discoverable flow with emptyallowCredentials
When to use¶
Use this when you want to implement WebAuthn server flows in Kotlin/JVM, with or without Ktor adapters.
How to use¶
import dev.webauthn.server.AuthenticationService
import dev.webauthn.server.InMemoryChallengeStore
import dev.webauthn.server.InMemoryCredentialStore
import dev.webauthn.server.InMemoryUserAccountStore
import dev.webauthn.server.RegistrationService
import dev.webauthn.server.crypto.JvmRpIdHasher
import dev.webauthn.server.crypto.JvmSignatureVerifier
import dev.webauthn.server.crypto.StrictAttestationVerifier
import dev.webauthn.model.ExperimentalWebAuthnL3Api
import dev.webauthn.serialization.KotlinxWebAuthnJsonCodec
/** Registration and authentication services sharing the same stores. */
data class PasskeyServices(
val registration: RegistrationService,
val authentication: AuthenticationService,
)
@OptIn(ExperimentalWebAuthnL3Api::class)
fun passkeyServices(): PasskeyServices {
val challengeStore = InMemoryChallengeStore()
val credentialStore = InMemoryCredentialStore()
val userStore = InMemoryUserAccountStore()
val registrationService = RegistrationService(
challengeStore = challengeStore,
credentialStore = credentialStore,
userAccountStore = userStore,
attestationVerifier = StrictAttestationVerifier(),
rpIdHasher = JvmRpIdHasher(),
clientDataDecoder = KotlinxWebAuthnJsonCodec(),
)
val authenticationService = AuthenticationService(
challengeStore = challengeStore,
credentialStore = credentialStore,
userAccountStore = userStore,
signatureVerifier = JvmSignatureVerifier(),
rpIdHasher = JvmRpIdHasher(),
clientDataDecoder = KotlinxWebAuthnJsonCodec(),
)
return PasskeyServices(registrationService, authenticationService)
}
Real-world scenario: run start/finish ceremonies in your backend service layer, then expose them via Ktor routes or your own HTTP transport.
How it fits¶
flowchart LR
KTOR["webauthn-server-ktor (optional)"] --> SVC["webauthn-server-core-jvm"]
SVC --> CORE["webauthn-core"]
SVC --> PROTOCOL["webauthn-protocol"]
SVC --> JSON_API["webauthn-json-api<br/>codec interface"]
JSON_KOTLINX["webauthn-json-kotlinx<br/>KotlinxWebAuthnJsonCodec"] --> JSON_API
SVC --> CRYPTO["webauthn-server-jvm-crypto or custom crypto-api implementation"]
SVC --> STORE["Challenge, credential, and account store contracts"]
IMPLEMENTATION["In-memory or Exposed store implementations"] --> STORE
Pitfalls and limits¶
- Services depend on correctly implemented store semantics (challenge expiry, credential lookup, counter updates).
- Registration and authentication keep shared fail-fast origin/session handling internally, so callers should expect matching origin-mismatch behavior across both ceremony types.
RegistrationService.finish()now returns a typed validation error when the user disappears between start and finish instead of throwing from the user store lookup.- Authentication challenge sessions allow nullable
userNamefor discoverable ceremonies, while named-mode finish still enforces credential ownership for the resolved account. - Finish requests carry only a byte-preserving
RawRegistrationResponseorRawAuthenticationResponse. The service derivesCollectedClientDatafrom that response's signedclientDataJSONthrough the injected neutral decoder, so custom transports cannot supply conflicting challenge, origin, or type values. - This module does not define your HTTP contract by itself.
Status¶
Beta ceremony orchestration with contract-tested behavior.