๐ค webauthn
amadan.net/rastrillo/rastrillo/webauthn
The passkey identity half: the WebAuthn ceremonies, a CBOR subset
reader, and the browser module. Most apps mount
passkey instead. This is what that is built
on, and what you reach for to build something else.
What it does and does not check
ES256 only. No attestation checking.
Skipping attestation is a decision. Attestation tells you which manufacturer made the authenticator, which matters for enterprise device policy and not for "is this the same key as last time". Verifying it means shipping and maintaining a root certificate store, and getting that wrong locks people out of their own accounts.
The CBOR reader implements the subset WebAuthn actually uses. It rejects anything outside it rather than attempting a general decode.
Config
type Config struct{ /* RPID, Origin, LegacyRPID, ... */ }
LegacyRPID accepts credentials minted under a previous hostname, so
moving domains does not invalidate everybody's passkeys. A credential
cannot be minted under the old name, only used, which keeps the escape
hatch from becoming a second permanent identity.
Register
func (c Config) Register(challenge, clientDataJSON, attestationObject []byte) (Credential, error)
Verifies a registration ceremony and returns the Credential to store โ
its id, public key and initial signature counter. A credential's public
key is public material: it verifies signatures and nothing else.
Verify
func (c Config) Verify(cred Credential, challenge, clientDataJSON, authData, signature []byte) (uint32, error)
Verifies an assertion and returns the new signature counter.
A counter going backwards is refused: that is the cloned-authenticator signal the spec provides. An authenticator that never counts, reporting zero every time, is allowed, because plenty do.
The errors
Each names one refusal, so you can tell them apart without matching message text:
| Error | Meaning |
|---|---|
ErrChallenge |
the challenge did not match |
ErrOrigin |
the client data's origin was wrong |
ErrRPID |
the relying-party id did not match |
ErrSignature |
the signature did not verify |
ErrCounter |
the signature counter went backwards |
ErrNotVerified |
the authenticator did not report user verification |
NewChallenge
func NewChallenge() ([]byte, error)
Fresh random challenge bytes. Store it server-side and use it once. The challenge is what makes an assertion un-replayable, so one that outlives a single ceremony undoes the whole protocol.
JS
func JS() []byte
The browser half as an embedded ES module. Serve it as a static asset.
It exposes register() and authenticate(), which produce exactly the
payloads Register and Verify expect.
authtest
webauthn/authtest is a fake authenticator, public precisely so your
own tests can drive a full ceremony without hardware.
Read this page as markdown โ exact, unstyled, and cheap for an agent to fetch.