Skip to main content
WebAuthn enables hardware-based authentication using security keys, platform authenticators (like Touch ID or Windows Hello), and other FIDO2-compliant devices.

Overview

The WebAuthn strategy provides:
  • Second-factor authentication (2FA/MFA)
  • First-factor passwordless authentication
  • Support for security keys (YubiKey, etc.)
  • Platform authenticators (Touch ID, Face ID, Windows Hello)
  • Multiple credentials per identity
WebAuthn can function as both first-factor (passwordless) and second-factor (MFA) authentication. For modern passwordless-only flows, consider using Passkeys instead.

Configuration

Enable WebAuthn

Configure WebAuthn in your Kratos configuration:
kratos.yml
selfservice:
  methods:
    webauthn:
      enabled: true
      config:
        passwordless: true  # Enable passwordless login
        rp:
          id: localhost
          display_name: Your Application Name
          # Note: use 'origin' for single origin or 'origins' for multiple
          origin: http://localhost:4455  # For single origin
          # OR use origins for multiple:
          # origins:
          #   - https://example.com
          #   - https://www.example.com

Passwordless vs. Second-factor

Users can register and login with only WebAuthn, no password required:
selfservice:
  methods:
    webauthn:
      enabled: true
      config:
        passwordless: true
        rp:
          id: localhost
          display_name: My App
          origin: http://localhost:4455
When passwordless: true:
  • WebAuthn is a first-factor credential (AAL1)
  • Users can login without a password
  • Credentials include user handle for identification

Relying Party configuration

The Relying Party (RP) configuration is critical:
kratos.yml
selfservice:
  methods:
    webauthn:
      config:
        rp:
          # Domain identifier (no protocol/port)
          id: example.com
          
          # Display name shown in authenticator UI
          display_name: Example Application
          
          # Single origin (legacy)
          origin: https://example.com
          
          # OR multiple origins (recommended)
          origins:
            - https://example.com
            - https://www.example.com
            - https://app.example.com
The rp.id must match the domain where WebAuthn is used. Set it to your top-level domain without protocol or port.

Identity schema configuration

Configure WebAuthn identifier

For passwordless WebAuthn, define the identifier field:
identity.schema.json
{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "type": "object",
  "properties": {
    "traits": {
      "type": "object",
      "properties": {
        "email": {
          "type": "string",
          "format": "email",
          "title": "E-Mail",
          "ory.sh/kratos": {
            "credentials": {
              "webauthn": {
                "identifier": true
              }
            },
            "verification": {
              "via": "email"
            },
            "recovery": {
              "via": "email"
            }
          }
        }
      },
      "required": ["email"]
    }
  }
}
The field marked with "identifier": true is used for passwordless login lookups.

Combined with other methods

You can combine WebAuthn with other authentication methods:
"email": {
  "type": "string",
  "format": "email",
  "title": "E-Mail",
  "ory.sh/kratos": {
    "credentials": {
      "password": {
        "identifier": true
      },
      "webauthn": {
        "identifier": true
      },
      "passkey": {
        "display_name": true
      }
    }
  }
}

User flows

Registration with WebAuthn

1

Initialize registration

Create a registration flow:
curl -X GET https://your-kratos-instance/self-service/registration/browser
2

Collect user information

If passwordless mode, collect the identifier (email):
curl -X POST https://your-kratos-instance/self-service/registration?flow=<flow-id> \
  -H "Content-Type: application/json" \
  -d '{
    "method": "webauthn",
    "traits": {
      "email": "[email protected]"
    }
  }'
3

Create credential

Use WebAuthn JavaScript API:
const publicKey = JSON.parse(webauthnRegisterTrigger);

const credential = await navigator.credentials.create({
  publicKey: publicKey
});
4

Submit credential

Submit the encoded credential response to complete registration.

Passwordless login

1

Initialize login

Create a login flow with WebAuthn challenge.
2

Request assertion

const publicKey = JSON.parse(webauthnLoginTrigger);

const assertion = await navigator.credentials.get({
  publicKey: publicKey
});
3

Submit assertion

Submit the assertion to authenticate the user.

Second-factor authentication

When passwordless: false, WebAuthn is used as a second factor:
1

Login with password

User first authenticates with password (or another first-factor method).
2

Initialize AAL2 flow

If AAL2 is required, Kratos requests second-factor authentication.
3

Complete WebAuthn challenge

User completes WebAuthn authentication to reach AAL2.

Managing WebAuthn credentials

Users can manage their credentials through settings:
# Add new credential
curl -X POST https://your-kratos-instance/self-service/settings?flow=<flow-id> \
  -H "Content-Type: application/json" \
  -H "Cookie: ory_kratos_session=<session-token>" \
  -d '{
    "method": "webauthn",
    "webauthn_register": "<credential-response>"
  }'

# Remove credential
curl -X POST https://your-kratos-instance/self-service/settings?flow=<flow-id> \
  -H "Content-Type: application/json" \
  -H "Cookie: ory_kratos_session=<session-token>" \
  -d '{
    "method": "webauthn",
    "webauthn_remove": "<credential-id>"
  }'

Security considerations

Authenticator types

WebAuthn supports multiple authenticator types:
  1. Platform authenticators
    • Built into devices (Touch ID, Face ID, Windows Hello)
    • More convenient for users
    • Tied to specific device
  2. Roaming authenticators
    • Security keys (YubiKey, etc.)
    • Portable across devices
    • Physical possession required

Attestation

WebAuthn supports attestation to verify authenticator provenance:
  • None - No attestation (default, most privacy-preserving)
  • Indirect - Anonymized attestation
  • Direct - Full attestation with manufacturer info
Kratos uses “none” by default for better privacy.

User verification

WebAuthn can require user verification (biometric or PIN):
  • Required - Always requires user verification
  • Preferred - Requests verification if available
  • Discouraged - Skips user verification
Kratos configures this based on the security requirements.

Credential counting logic

The strategy implements different credential counting based on passwordless mode (see selfservice/strategy/webauthn/strategy.go:80-110):
// First-factor counting (passwordless mode)
func (s *Strategy) CountActiveFirstFactorCredentials(
  _ context.Context, 
  cc map[identity.CredentialsType]identity.Credentials,
) (count int, err error) {
  return s.countCredentials(cc, true)  // Only passwordless credentials
}

// Second-factor counting (MFA mode)
func (s *Strategy) CountActiveMultiFactorCredentials(
  _ context.Context, 
  cc map[identity.CredentialsType]identity.Credentials,
) (count int, err error) {
  return s.countCredentials(cc, false)  // Only non-passwordless credentials
}
Passwordless credentials require an identifier to be set (see selfservice/strategy/webauthn/strategy.go:96-100).

Platform support

WebAuthn is supported on:
  • Desktop: Chrome 67+, Edge 18+, Firefox 60+, Safari 13+
  • Mobile: iOS 14.5+, Android 7+
  • Security Keys: FIDO2/WebAuthn compliant keys (YubiKey 5, Titan, etc.)

API reference

Strategy implementation

  • Strategy ID: webauthn
  • Node Group: webauthn group in UI nodes
  • AAL Level:
    • AAL1 when passwordless: true (first factor)
    • AAL2 when passwordless: false (second factor)
See selfservice/strategy/webauthn/strategy.go:76-78 for the strategy structure.

Configuration options

selfservice:
  methods:
    webauthn:
      enabled: true
      config:
        passwordless: true
        rp:
          id: example.com
          display_name: My Application
          origins:
            - https://example.com

Migration from WebAuthn to Passkeys

If you’re using WebAuthn in passwordless mode, consider migrating to the Passkeys strategy:
  • Passkeys are purpose-built for passwordless flows
  • Better cross-device synchronization
  • Clearer separation from MFA use cases
  • Modern terminology users understand
Both strategies use WebAuthn protocol under the hood, but Passkeys is optimized for the passwordless experience.

Next steps

Build docs developers (and LLMs) love