Skip to content
ecdsa.com
Go

go-webauthn

go-webauthn, the WebAuthn relying-party library for Go: DER-only assertion signatures, user verification driven by the session, and counter regressions reported as a clone warning instead of a failed login.
License
BSD-3-Clause
Checked version
0.18.2 ·
Source
github.com/go-webauthn/webauthn
Documentation
pkg.go.dev/github.com/go-webauthn/webauthn/webauthn

Compatibility at a glance

WebAuthn

github.com/go-webauthn/webauthn (the maintained fork of duo-labs/webauthn) runs both ceremonies — BeginRegistration/FinishRegistration and BeginLogin/FinishLogin or their Validate* variants for frameworks other than net/http — and verifies every attestation format. The check validated the same software-authenticator responses used for SimpleWebAuthn, py_webauthn and WebAuthn4J.

Policy comes from the configuration and is carried in SessionData. With an empty AuthenticatorSelection, BeginRegistration sent an empty object, so browsers apply the defaults — user verification preferred, resident key discouraged — and validation accepted responses with the UV flag clear because the session asked only for preferred. With UserVerification: required in the session the same assertion failed with User verification required but flag not set by authenticator. Set the policy in Config.AuthenticatorSelection, as the sample does, and store the whole SessionData between the two calls.

Counter handling is the most important difference from the other libraries. An assertion whose counter (3) was lower than the stored one (5) logged in successfully: the returned credential had Authenticator.CloneWarning set and its SignCount left at 5. The library only reports a possible clone; whether to refuse the login is up to your code, which must check CloneWarning after every login.

Assertion signatures must be ASN.1 DER — a raw 64-byte r‖s signature failed with Error validating the assertion signature: Signature invalid or not provided — and a high-S DER signature verified. The default pubKeyCredParams list is long: ES256, ES384 and ES512 (-7, -35, -36), RS256 to RS512 (-257 to -259), PS256 to PS512 (-37 to -39) and Ed25519 (-8); the Ed25519 credential verified end to end. There is no ML-DSA entry in 0.18.2.

Decode what a browser sends with the WebAuthn inspector, look up authenticator models with AAGUID lookup, and try ceremonies in the passkey sandbox.

Facts and the checks behind them

Nothing below is copied from documentation. The check content/libraries/verify/go-webauthn/run.sh ran go-webauthn 0.18.2 in the container image golang:1.27.3-trixie on October 20, 2026; the page must match what it printed, and each fact lists those lines.

  • Default COSE algorithms

    -7-35-36-257-258-259-37-38-39-8

    BeginRegistration lists COSE algorithms -7, -35, -36 (ES256/384/512), -257, -258, -259 (RS256/384/512), -37, -38, -39 (PS256/384/512) and -8 (Ed25519).

    Recorded by the check
    fact webauthn-default-algorithms = -7, -35, -36, -257, -258, -259, -37, -38, -39, -8
  • Default attestation

    none

    Attestation conveyance defaults to none.

    Recorded by the check
    fact webauthn-attestation = none
  • Default user verification

    preferred

    authenticatorSelection is sent as an empty object by default, so the browser applies preferred.

    Recorded by the check
    fact webauthn-user-verification = preferred
    evidence webauthn-user-verification: authenticatorSelection in BeginRegistration output: {}
  • Default resident key

    discouraged

    With no residentKey in the options, the WebAuthn default discouraged applies.

    Recorded by the check
    fact webauthn-resident-key = discouraged
  • User verification enforced by default

    no

    With the session's user verification at preferred, a registration and an assertion with only UP set verified; with required the assertion failed with User verification required but flag not set by authenticator.

    Recorded by the check
    fact webauthn-uv-enforced-by-default = no
    evidence webauthn-uv-enforced-by-default: registration with the UV flag clear, session UV preferred: verified
  • Assertion signature encoding

    der

    A correct ES256 assertion with a DER signature verified.

    Recorded by the check
    fact webauthn-assertion-encoding = der
  • Raw r‖s assertion signature

    rejected

    A raw 64-byte r‖s signature failed with Error validating the assertion signature: Signature invalid or not provided.

    Recorded by the check
    fact webauthn-raw-signature = rejected
  • High-S assertion signature

    accepted

    A DER signature with s replaced by n − s verified.

    Recorded by the check
    fact webauthn-high-s = accepted
  • Signature counter going back

    flagged

    A counter of 3 after a stored 5 did not fail the login: the credential came back with CloneWarning true and SignCount still 5.

    Recorded by the check
    fact webauthn-counter-regression = flagged
    evidence webauthn-counter-regression: counter 3 after a stored 5: login succeeded, CloneWarning true, SignCount now 5
    evidence webauthn-counter-regression: counter 0 after a stored 0: verified, CloneWarning false
  • Ed25519 credentials

    supported

    An Ed25519 (COSE -8) credential registered and authenticated successfully.

    Recorded by the check
    fact webauthn-ed25519 = supported
    evidence webauthn-ed25519: verified

Usage

Configure the relying party to require user verification and discoverable credentials, and see the policy in the options and the session.
package main

import (
	"fmt"

	"github.com/go-webauthn/webauthn/protocol"
	"github.com/go-webauthn/webauthn/webauthn"
)

type user struct{}

func (user) WebAuthnID() []byte                         { return []byte("user-1") }
func (user) WebAuthnName() string                       { return "user@example.com" }
func (user) WebAuthnDisplayName() string                { return "User" }
func (user) WebAuthnCredentials() []webauthn.Credential { return nil }

func main() {
	w, _ := webauthn.New(&webauthn.Config{
		RPID: "example.com", RPDisplayName: "Example", RPOrigins: []string{"https://example.com"},
		// Without this, options say nothing about UV or discoverable credentials.
		AuthenticatorSelection: protocol.AuthenticatorSelection{
			ResidentKey: protocol.ResidentKeyRequirementRequired, UserVerification: protocol.VerificationRequired,
		},
	})
	creation, session, _ := w.BeginRegistration(user{})
	// The session carries the requirement into FinishRegistration/FinishLogin.
	fmt.Println("uv:", creation.Response.AuthenticatorSelection.UserVerification, session.UserVerification)
	fmt.Println("resident key:", creation.Response.AuthenticatorSelection.ResidentKey)
}

The snippet harness runs every sample verbatim in the container image golang:1.27.3-trixie, and the run fails unless the output matches.

Command
GOFLAGS=-mod=mod go run .
Output must match (regular expression)
uv: required required\nresident key: required

Libraries