<!--
This file was generated by Spark. Do not edit it by hand.
-->
# AshAuthentication.Strategy.WebAuthn

Strategy for authenticating using [WebAuthn/FIDO2](https://webauthn.io/) hardware
security keys and passkeys.

This strategy supports:

- Hardware security keys (YubiKey, etc.)
- Platform authenticators (Touch ID, Windows Hello, Face ID)
- Discoverable credentials / passkeys
- Multi-tenancy (dynamic `rp_id` per tenant)

Credentials are stored in a separate Ash resource that you define. The strategy
auto-generates actions on both the user resource and the credential resource for
registration, sign-in, credential management, and challenge generation.

## Modes

The strategy can be configured for two roles via the `registration_enabled?`,
`sign_in_enabled?`, and `verify_enabled?` flags:

- **Primary** (default; all three flags `true`) — passkeys are the primary
  credential. Users register and sign in directly with their authenticator.
- **Second factor** (`registration_enabled? false`, `sign_in_enabled? false`,
  `verify_enabled? true`) — passkeys are only used as a second factor on top
  of another primary credential (e.g. password). The strategy doesn't
  register or sign in users directly; it only verifies an assertion against
  the *currently authenticated* user. On successful verification, a
  `webauthn_verified_at` claim is added to the user's authentication token
  so protected routes can require it.

See the
[Passkeys as 2FA](https://hexdocs.pm/ash_authentication_phoenix/webauthn-2fa.html)
guide for the second-factor flow end to end.

## Quick Start

```elixir
defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshAuthentication],
    domain: MyApp.Accounts

  attributes do
    uuid_primary_key :id
    attribute :email, :ci_string, allow_nil?: false
  end

  authentication do
    tokens do
      enabled? true
      token_resource MyApp.Accounts.Token
      signing_secret fn _, _ -> {:ok, Application.get_env(:my_app, :token_secret)} end
    end

    strategies do
      webauthn :webauthn do
        credential_resource MyApp.Accounts.Credential
        rp_id "example.com"
        rp_name "My App"
        origin "https://example.com"
        identity_field :email
      end
    end
  end

  relationships do
    has_many :webauthn_credentials, MyApp.Accounts.Credential
  end

  identities do
    identity :unique_email, [:email]
  end
end
```

## Origin Configuration

The **origin** is the full URL the browser sends during WebAuthn ceremonies
(scheme + domain + port). The **rp_id** is the domain name only. These are
related but distinct:

| Setting   | Example                    | What it is                       |
|-----------|----------------------------|----------------------------------|
| `rp_id`   | `"example.com"`            | Domain only (Relying Party ID)   |
| `origin`  | `"https://example.com"`    | Full URL including scheme + port |

If `origin` is not set, it defaults to `"https://{rp_id}"`. This works for
production on standard port 443, but **breaks in development** because the
browser includes the port in the origin and `Wax` will reject the mismatch.

### Production

    origin "https://example.com"

### Development (non-standard port)

    origin "https://localhost:4001"

### Multi-tenant (dynamic per tenant)

    origin {MyApp.WebAuthn, :origin_for_tenant, []}

## Credential Resource

You must define a separate Ash resource to store WebAuthn credentials. It needs:

- `credential_id` (`:binary`) - the raw credential ID from the authenticator
- `public_key` (`AshAuthentication.Strategy.WebAuthn.CoseKey`) - the COSE public key
- `sign_count` (`:integer`) - replay attack counter
- `label` (`:string`) - user-facing name for the credential
- `last_used_at` (`:utc_datetime_usec`, optional) - tracks last authentication time
- A `belongs_to` relationship to your user resource
- A policy bypass for `AshAuthentication.Checks.AshAuthenticationInteraction`

### Full Example

```elixir
defmodule MyApp.Accounts.Credential do
  use Ash.Resource,
    domain: MyApp.Accounts,
    data_layer: AshPostgres.DataLayer,
    authorizers: [Ash.Policy.Authorizer]

  postgres do
    table "webauthn_credentials"
    repo(MyApp.Repo)
  end

  policies do
    bypass AshAuthentication.Checks.AshAuthenticationInteraction do
      authorize_if always()
    end

    policy always() do
      authorize_if always()
    end
  end

  attributes do
    uuid_primary_key :id
    attribute :credential_id, :binary, allow_nil?: false, public?: true

    attribute :public_key, AshAuthentication.Strategy.WebAuthn.CoseKey,
      allow_nil?: false, public?: true

    attribute :sign_count, :integer, default: 0, allow_nil?: false, public?: true
    attribute :label, :string, default: "Security Key", public?: true
    attribute :last_used_at, :utc_datetime_usec, public?: true
    create_timestamp :inserted_at
    update_timestamp :updated_at
  end

  relationships do
    belongs_to :user, MyApp.Accounts.User, allow_nil?: false, public?: true
  end

  identities do
    identity :unique_credential_id, [:credential_id]
  end

  actions do
    defaults [:read, :destroy]

    create :create do
      primary? true
      accept [:credential_id, :public_key, :sign_count, :label, :user_id]
    end

    update :update do
      primary? true
      accept [:sign_count, :label, :last_used_at]
    end
  end
end
```

## Token Configuration

Tokens **must** be enabled for WebAuthn to work. The `signing_secret` callback
must return an `{:ok, value}` tuple, not a raw string:

```elixir
authentication do
  tokens do
    enabled? true
    token_resource MyApp.Accounts.Token
    signing_secret fn _, _ -> {:ok, Application.get_env(:my_app, :token_secret)} end
  end
end
```

## Adding Credentials to Existing Users

The built-in `register` action creates a **new user** with a credential. To add
a passkey to an already-authenticated user, you need a custom controller that:

1. Generates a registration challenge (via `Wax.new_registration_challenge/1`)
2. Sends it to the browser
3. Receives the attestation response
4. Calls `Wax.register/3` to verify it
5. Stores the credential directly on the credential resource

This is intentional -- the strategy's register flow is for new user sign-up,
not for adding keys to existing accounts.

## Accessing the User After Authentication

After successful WebAuthn sign-in, the JWT is available in user metadata:

```elixir
token = user.__metadata__[:token]
```

To load a user from a token (e.g., in a LiveView `mount`):

```elixir
{:ok, user} = AshAuthentication.subject_to_user(
  "user?id=#{user_id}",
  MyApp.Accounts.User
)
```

## Multi-Tenancy

For multi-tenant applications, `rp_id`, `rp_name`, and `origin` all accept
MFA tuples that receive the tenant as the first argument:

```elixir
webauthn :webauthn do
  credential_resource MyApp.Accounts.Credential
  rp_id {MyApp.WebAuthn, :rp_id_for_tenant, []}
  rp_name {MyApp.WebAuthn, :rp_name_for_tenant, []}
  origin {MyApp.WebAuthn, :origin_for_tenant, []}
  identity_field :email
end
```

Then implement the callbacks:

```elixir
defmodule MyApp.WebAuthn do
  def rp_id_for_tenant(tenant), do: "#{tenant}.example.com"
  def rp_name_for_tenant(tenant), do: "MyApp - #{tenant}"
  def origin_for_tenant(tenant), do: "https://#{tenant}.example.com"
end
```

## Gotchas

- **Origin must include the port** for non-standard ports (e.g., `"https://localhost:4001"`).
  The default derivation from `rp_id` produces `"https://{rp_id}"` which omits the port.
- **Signing secret must return `{:ok, value}`**, not a raw string. A common mistake
  is `fn _, _ -> "my_secret" end` -- it must be `fn _, _ -> {:ok, "my_secret"} end`.
- **Challenge data is stored in the session as plain maps**, not `Wax.Challenge` structs,
  because cookie session stores cannot serialize arbitrary Elixir structs. The plug
  reconstructs the struct before passing it to Wax.
- **`add_credential` (adding a key to an existing user) is not built-in.** The `register`
  action creates a new user. See "Adding Credentials to Existing Users" above.
- **`origin_verify_fun`** is hardcoded to `{Wax, :origins_match?, []}` when
  reconstructing challenges from the session. If you need custom origin verification,
  you'll need to override the plug.
- **Token generation happens in `Actions.sign_in`** via `Jwt.token_for_user/3`, not in
  an Ash preparation like the Password strategy. This is because Wax verification
  happens outside the Ash action pipeline.



### authentication.strategies.webauthn
```elixir
webauthn name \\ :webauthn
```


Strategy for authenticating using WebAuthn/FIDO2 hardware security keys and passkeys.




### Examples
```
webauthn :webauthn do
  credential_resource MyApp.Accounts.Credential
  rp_id "example.com"
  rp_name "My App"
  identity_field :email
end

```




### Options

| Name | Type | Default | Docs |
|------|------|---------|------|
| [`credential_resource`](#authentication-strategies-webauthn-credential_resource){: #authentication-strategies-webauthn-credential_resource .spark-required} | `atom \| module` |  | The Ash resource used to store WebAuthn credentials. Must have `credential_id` (binary), `public_key` (binary), and `sign_count` (integer) attributes, plus a `belongs_to` relationship to the user resource. |
| [`rp_id`](#authentication-strategies-webauthn-rp_id){: #authentication-strategies-webauthn-rp_id .spark-required} | `String.t \| (any -> any) \| mfa \| (any, any -> any) \| module` |  | Relying Party ID - your domain name (e.g. "example.com"). For multi-tenant setups, pass an MFA tuple or 1-arity function that receives the tenant and returns the domain string:     rp_id {MyApp.WebAuthn, :rp_id_for_tenant, []} For application-environment-driven configuration, point at a module implementing `AshAuthentication.Secret`:     rp_id MyApp.Secrets |
| [`rp_name`](#authentication-strategies-webauthn-rp_name){: #authentication-strategies-webauthn-rp_name .spark-required} | `String.t \| (any -> any) \| mfa \| (any, any -> any) \| module` |  | Relying Party display name shown to the user during registration. For multi-tenant setups, pass an MFA tuple or 1-arity function:     rp_name {MyApp.WebAuthn, :rp_name_for_tenant, []} For application-environment-driven configuration, point at a module implementing `AshAuthentication.Secret`:     rp_name MyApp.Secrets |
| [`origin`](#authentication-strategies-webauthn-origin){: #authentication-strategies-webauthn-origin } | `String.t \| (any -> any) \| mfa \| (any, any -> any) \| module` |  | The expected origin for WebAuthn ceremonies. In WebAuthn, the **origin** is the scheme + domain + port that the browser reports during registration and authentication. It is distinct from `rp_id`: - `rp_id` = domain only (e.g. `"example.com"`) - `origin` = full URL (e.g. `"https://example.com"` or `"https://localhost:4001"`) If not set, defaults to `"https://{rp_id}"`. This default **omits the port**, which works for production on port 443 but will cause Wax to reject ceremonies in development where the port is non-standard. **Production:**     origin "https://example.com" **Development (non-standard port):**     origin "https://localhost:4001" **Dynamic (multi-tenant):**     origin {MyApp.WebAuthn, :origin_for_tenant, []} **Application-environment-driven:**     origin MyApp.Secrets |
| [`identity_field`](#authentication-strategies-webauthn-identity_field){: #authentication-strategies-webauthn-identity_field } | `atom` | `:email` | The name of the attribute which uniquely identifies the user (e.g. `:email`). Used for looking up the user during authentication. |
| [`authenticator_attachment`](#authentication-strategies-webauthn-authenticator_attachment){: #authentication-strategies-webauthn-authenticator_attachment } | `nil \| :platform \| :cross_platform` |  | Restricts authenticator type. `nil` allows any, `:platform` limits to built-in (Touch ID, Windows Hello), `:cross_platform` limits to USB/NFC keys (YubiKey). |
| [`user_verification`](#authentication-strategies-webauthn-user_verification){: #authentication-strategies-webauthn-user_verification } | `"required" \| "preferred" \| "discouraged"` | `"preferred"` | Whether user verification (PIN/biometric) is required. Use `"required"` for highest security. |
| [`attestation`](#authentication-strategies-webauthn-attestation){: #authentication-strategies-webauthn-attestation } | `"none" \| "direct"` | `"none"` | Attestation conveyance preference. `"none"` is recommended for most use cases. `"direct"` requests the authenticator's attestation certificate. |
| [`timeout`](#authentication-strategies-webauthn-timeout){: #authentication-strategies-webauthn-timeout } | `pos_integer` | `60000` | Timeout for WebAuthn ceremonies in milliseconds. |
| [`resident_key`](#authentication-strategies-webauthn-resident_key){: #authentication-strategies-webauthn-resident_key } | `:required \| :preferred \| :discouraged` | `:required` | Whether to require discoverable credentials (passkeys). `:required` enables username-less authentication. |
| [`credential_id_field`](#authentication-strategies-webauthn-credential_id_field){: #authentication-strategies-webauthn-credential_id_field } | `atom` | `:credential_id` | The name of the credential ID attribute on the credential resource. |
| [`public_key_field`](#authentication-strategies-webauthn-public_key_field){: #authentication-strategies-webauthn-public_key_field } | `atom` | `:public_key` | The name of the public key attribute on the credential resource. |
| [`sign_count_field`](#authentication-strategies-webauthn-sign_count_field){: #authentication-strategies-webauthn-sign_count_field } | `atom` | `:sign_count` | The name of the sign count attribute on the credential resource. |
| [`label_field`](#authentication-strategies-webauthn-label_field){: #authentication-strategies-webauthn-label_field } | `atom` | `:label` | The name of the label attribute on the credential resource. |
| [`last_used_at_field`](#authentication-strategies-webauthn-last_used_at_field){: #authentication-strategies-webauthn-last_used_at_field } | `atom` | `:last_used_at` | The name of the last_used_at attribute on the credential resource. Set to `nil` to disable tracking. |
| [`user_relationship_name`](#authentication-strategies-webauthn-user_relationship_name){: #authentication-strategies-webauthn-user_relationship_name } | `atom` | `:user` | The name of the belongs_to relationship on the credential resource pointing to the user. |
| [`credentials_relationship_name`](#authentication-strategies-webauthn-credentials_relationship_name){: #authentication-strategies-webauthn-credentials_relationship_name } | `atom` | `:webauthn_credentials` | The name of the has_many relationship on the user resource pointing to credentials. |
| [`registration_enabled?`](#authentication-strategies-webauthn-registration_enabled?){: #authentication-strategies-webauthn-registration_enabled? } | `boolean` | `true` | Whether to allow new user registration via WebAuthn. |
| [`sign_in_enabled?`](#authentication-strategies-webauthn-sign_in_enabled?){: #authentication-strategies-webauthn-sign_in_enabled? } | `boolean` | `true` | Whether the strategy can sign users in directly (i.e. WebAuthn is the primary credential). Set to `false` when using WebAuthn purely as a second factor. |
| [`verify_enabled?`](#authentication-strategies-webauthn-verify_enabled?){: #authentication-strategies-webauthn-verify_enabled? } | `boolean` | `true` | Whether the strategy exposes a `:verify` phase that proves possession of a passkey for an already-authenticated user. Used for second-factor and step-up flows. |
| [`register_action_name`](#authentication-strategies-webauthn-register_action_name){: #authentication-strategies-webauthn-register_action_name } | `atom` |  | The name of the register action on the user resource. Defaults to `register_with_<strategy_name>`. |
| [`sign_in_action_name`](#authentication-strategies-webauthn-sign_in_action_name){: #authentication-strategies-webauthn-sign_in_action_name } | `atom` |  | The name of the sign-in action on the user resource. Defaults to `sign_in_with_<strategy_name>`. |
| [`sign_in_with_token_action_name`](#authentication-strategies-webauthn-sign_in_with_token_action_name){: #authentication-strategies-webauthn-sign_in_with_token_action_name } | `atom` |  | The name of the action used to sign in with a short-lived token issued by a successful WebAuthn ceremony. Defaults to `sign_in_with_<strategy_name>_token`. |
| [`verify_action_name`](#authentication-strategies-webauthn-verify_action_name){: #authentication-strategies-webauthn-verify_action_name } | `atom` |  | The name of the second-factor verify action on the user resource. Defaults to `verify_<strategy_name>`. |
| [`store_credential_action_name`](#authentication-strategies-webauthn-store_credential_action_name){: #authentication-strategies-webauthn-store_credential_action_name } | `atom` |  | The name of the create action on the credential resource. Defaults to `store_<strategy_name>_credential`. |
| [`update_sign_count_action_name`](#authentication-strategies-webauthn-update_sign_count_action_name){: #authentication-strategies-webauthn-update_sign_count_action_name } | `atom` |  | The name of the update action for sign_count on the credential resource. Defaults to `update_<strategy_name>_sign_count`. |
| [`list_credentials_action_name`](#authentication-strategies-webauthn-list_credentials_action_name){: #authentication-strategies-webauthn-list_credentials_action_name } | `atom` |  | The name of the read action to list credentials. Defaults to `list_<strategy_name>_credentials`. |
| [`delete_credential_action_name`](#authentication-strategies-webauthn-delete_credential_action_name){: #authentication-strategies-webauthn-delete_credential_action_name } | `atom` |  | The name of the destroy action for credentials. Defaults to `delete_<strategy_name>_credential`. |
| [`update_credential_label_action_name`](#authentication-strategies-webauthn-update_credential_label_action_name){: #authentication-strategies-webauthn-update_credential_label_action_name } | `atom` |  | The name of the update action for credential labels. Defaults to `update_<strategy_name>_credential_label`. |
| [`add_credential_action_name`](#authentication-strategies-webauthn-add_credential_action_name){: #authentication-strategies-webauthn-add_credential_action_name } | `atom` |  | The name of the action to add a credential to an existing user. Defaults to `add_<strategy_name>_credential`. |





### Introspection

Target: `AshAuthentication.Strategy.WebAuthn`



<style type="text/css">.spark-required::after { content: "*"; color: red !important; }</style>
