webauthn-core¶
Audience: teams validating WebAuthn ceremonies before cryptographic verification and persistence updates.
What it provides¶
- Validation for
clientDataexpectations (type,challenge,origin, optional related origins). - Validation for
authenticatorDataflags and signature counter progression. - Allow-list enforcement for authentication (
allowCredentials) via credential ID checks. - Extension hook contracts for optional L3 extension checks.
- Composable per-extension validation hooks (
PrfExtensionHook,LargeBlobExtensionHook). CompositeExtensionHookfor mix-and-match extension validation pipelines.
flowchart TD
Start["Typed finish input<br/>RegistrationValidationInput / AuthenticationValidationInput"] --> ClientData["validateClientData"]
ClientData --> AuthData["validateAuthenticatorData"]
AuthData --> AllowCred["requireAllowedCredential"]
AllowCred --> Hook["WebAuthnExtensionHook (optional)"]
Hook --> CoreOut["ValidationResult output"]
CoreOut --> Crypto["Server crypto verification<br/>signature + attestation"]
Crypto --> Persist["Store signCount / credential state"]
Where it fits in a real ceremony¶
Use webauthn-core in server finish endpoints after parsing transport payloads into model types and before signature/attestation verification. It gives you standards-aligned preconditions and typed output values (credentialId, signCount, extension outputs) for downstream steps.
How to use¶
A practical authentication finish path usually chains core validation, allow-list checks, extension checks, then crypto verification and persistence.
import dev.webauthn.core.AuthenticationValidationInput
import dev.webauthn.core.AuthenticationValidationOutput
import dev.webauthn.core.WebAuthnCoreValidator
import dev.webauthn.core.WebAuthnExtensionHook
import dev.webauthn.core.WebAuthnExtensionValidator
import dev.webauthn.model.CredentialId
import dev.webauthn.model.ExperimentalWebAuthnL3Api
import dev.webauthn.model.ValidationResult
/**
* Chains core authentication checks, then requires cryptographic signature
* verification before returning a successful finish result.
*
* Core validation alone is not enough to accept an assertion. Callers must
* supply a [verifySignature] implementation (for example JVM crypto) that
* receives the assertion input and validated output, constructs the signed
* bytes from [AuthenticationValidationInput.response], and succeeds before
* [ValidationResult.Valid] is returned.
*/
@OptIn(ExperimentalWebAuthnL3Api::class)
suspend fun validateAssertionForFinish(
input: AuthenticationValidationInput,
allowedCredentialIds: Set<CredentialId>,
verifySignature: suspend (
AuthenticationValidationInput,
AuthenticationValidationOutput,
) -> ValidationResult<Unit>,
extensionHook: WebAuthnExtensionHook = WebAuthnExtensionValidator,
): ValidationResult<Long> {
val core = WebAuthnCoreValidator.validateAuthentication(input)
if (core is ValidationResult.Invalid) return core
val output = (core as ValidationResult.Valid).value
val allow = WebAuthnCoreValidator.requireAllowedCredential(
response = input.response,
allowedCredentialIds = allowedCredentialIds,
)
if (allow is ValidationResult.Invalid) return allow
val ext = extensionHook.validateAuthenticationExtensions(
inputs = input.options.extensions,
outputs = output.extensions,
)
if (ext is ValidationResult.Invalid) return ext
val signature = verifySignature(input, output)
if (signature is ValidationResult.Invalid) return signature
// Persist output.signCount only after signature verification succeeds.
return ValidationResult.Valid(output.signCount)
}
Important API behavior:
validateRegistration(...)/validateAuthentication(...)return typed outputs for downstream persistence.allowedOriginsonly broadens origin acceptance when explicitly provided.previousSignCountmust come from server-trusted credential state.ChallengeSession.userNameis nullable so discoverable (username-less) authentication ceremonies can be represented without synthetic identity fields.CompositeExtensionHookpreserves invalid outcomes even when a hook returnsValidationResult.Invalid(emptyList()).- Kotlin consumers that enable
-Xreturn-value-checker=checkare warned when core validation or extension-hook results are ignored. - This module does not verify signatures or attestation statements.
Composable extension hooks¶
Each L3 extension ships as a standalone WebAuthnExtensionHook implementation:
| Hook | Extension | Spec Section | Notes |
|---|---|---|---|
PrfExtensionHook |
HMAC Secret (prf) | §9.2.1 | Authentication validates global eval requirements only; per-credential evalByCredential checks require selected credential ID |
LargeBlobExtensionHook |
Large blob storage | §9.2.2 |
WebAuthnExtensionValidator includes both by default. For custom pipelines, use CompositeExtensionHook:
import dev.webauthn.core.CompositeExtensionHook
import dev.webauthn.core.PrfExtensionHook
import dev.webauthn.model.AuthenticationExtensionsClientInputs
import dev.webauthn.model.AuthenticationExtensionsClientOutputs
import dev.webauthn.model.ValidationResult
@OptIn(ExperimentalWebAuthnL3Api::class)
fun validatePrfOnly(
inputs: AuthenticationExtensionsClientInputs?,
outputs: AuthenticationExtensionsClientOutputs?,
): ValidationResult<Unit> {
val prfOnly = CompositeExtensionHook([PrfExtensionHook])
return prfOnly.validateAuthenticationExtensions(inputs, outputs)
}
Pitfalls and limits¶
- No storage/challenge lifecycle management.
- No JSON/CBOR parsing or transport DTO mapping.
- No crypto backend execution (delegated to
webauthn-crypto-apiimplementations).
iOS targets¶
- Published Apple targets are
iosArm64andiosSimulatorArm64. iosX64support was removed to align with upstream dependency artifacts and current CI target compatibility.
Status¶
Beta, core validation engine.