Packages
ash_authentication
5.0.0-rc.5
5.0.0-rc.12
5.0.0-rc.11
5.0.0-rc.10
5.0.0-rc.9
5.0.0-rc.8
5.0.0-rc.7
5.0.0-rc.6
5.0.0-rc.5
5.0.0-rc.4
5.0.0-rc.3
5.0.0-rc.2
5.0.0-rc.1
5.0.0-rc.0
4.14.1
4.14.0
4.13.7
4.13.6
4.13.5
4.13.4
4.13.3
4.13.2
retired
4.13.1
retired
4.13.0
4.12.0
4.11.0
4.10.0
4.9.9
4.9.8
4.9.7
4.9.6
4.9.5
4.9.4
4.9.3
4.9.2
4.9.1
4.9.0
4.8.7
4.8.6
4.8.5
4.8.3
4.8.2
4.8.1
4.8.0
4.7.6
4.7.5
4.7.4
4.7.3
4.7.2
4.7.1
4.7.0
4.6.4
4.6.3
4.6.2
4.6.1
4.6.0
4.5.6
4.5.5
4.5.4
4.5.3
4.5.2
4.5.1
4.5.0
4.4.9
4.4.8
4.4.7
4.4.6
4.4.5
4.4.4
4.4.3
4.4.2
4.4.1
4.4.0
4.3.12
4.3.11
4.3.10
4.3.9
4.3.8
4.3.7
4.3.6
4.3.5
4.3.4
4.3.3
4.3.2
4.3.1
4.3.0
4.2.7
4.2.6
4.2.5
4.2.4
4.2.3
4.2.2
4.2.1
4.2.0
4.1.0
4.0.4
4.0.3
4.0.2
4.0.1
4.0.0
4.0.0-rc.6
4.0.0-rc.5
4.0.0-rc.3
4.0.0-rc.2
4.0.0-rc.1
4.0.0-rc.0
3.12.4
3.12.3
3.12.2
3.12.1
3.12.0
3.11.16
3.11.15
3.11.14
3.11.13
3.11.12
3.11.11
3.11.10
3.11.9
3.11.8
3.11.7
3.11.6
3.11.5
3.11.4
3.11.3
3.11.2
3.11.1
3.11.0
3.10.8
3.10.7
3.10.6
3.10.5
3.10.4
3.10.3
3.10.2
3.10.1
3.10.0
3.9.6
3.9.5
3.9.4
3.9.3
3.9.2
3.9.1
3.9.0
3.8.0
3.7.9
3.7.8
3.7.6
3.7.5
3.7.4
3.7.3
3.7.2
3.7.1
3.7.0
3.6.1
3.6.0
3.5.3
3.5.2
3.5.1
3.5.0
3.3.1
3.3.0
3.2.2
3.2.1
3.2.0
3.1.0
3.0.3
Authentication extension for the Ash Framework.
Security advisory:
This version has known vulnerabilities.
View advisories
Current section
Files
Jump to
Current section
Files
documentation/tutorials/totp.md
<!--
SPDX-FileCopyrightText: 2025 Alembic Pty Ltd
SPDX-License-Identifier: MIT
-->
# TOTP (Time-based One-Time Password) Tutorial
TOTP allows users to authenticate using time-based one-time passwords generated
by authenticator apps like Google Authenticator, Authy, or 1Password.
## Use Cases
TOTP can be used in two ways:
1. **Two-Factor Authentication (2FA)** - As a second factor after password authentication
2. **Standalone Authentication** - As the primary authentication method (passwordless)
This tutorial covers both approaches.
## Prerequisites
- AshAuthentication configured with a User resource
- A token resource if using `confirm_setup_enabled?` (recommended)
## Add Required Attributes
Add the following attributes to your User resource:
```elixir
# lib/my_app/accounts/user.ex
attributes do
# ... existing attributes ...
attribute :totp_secret, :binary do
allow_nil? true
sensitive? true
public? false
end
attribute :last_totp_at, :utc_datetime do
allow_nil? true
sensitive? true
public? false
end
end
```
The `totp_secret` stores the shared secret, and `last_totp_at` prevents replay
attacks by tracking the last successful authentication time.
## Basic TOTP Setup (2FA Mode)
For 2FA, users set up TOTP after registering with another method (like password):
```elixir
# lib/my_app/accounts/user.ex
authentication do
strategies do
password :password do
identity_field :email
end
totp do
identity_field :email
# Required: choose a brute force protection strategy
brute_force_strategy {:preparation, MyApp.TotpBruteForcePreparation}
end
end
end
```
This generates:
- `setup_with_totp` action - generates a secret and stores it on the user
- `verify_with_totp` action - verifies a code without signing in
- `totp_url_for_totp` calculation - generates the `otpauth://` URL for QR codes
### Brute Force Protection
TOTP requires a brute force protection strategy. Options:
**1. Custom Preparation (simplest)**
```elixir
brute_force_strategy {:preparation, MyApp.TotpBruteForcePreparation}
```
Create a preparation that implements your protection logic:
```elixir
# lib/my_app/accounts/totp_brute_force_preparation.ex
defmodule MyApp.TotpBruteForcePreparation do
use Ash.Resource.Preparation
def prepare(query, _opts, _context) do
# Implement rate limiting, account lockout, etc.
# Return the query unchanged if allowed to proceed
query
end
end
```
**2. Rate Limiting (with AshRateLimiter)**
```elixir
brute_force_strategy :rate_limit
```
Requires the `AshRateLimiter` extension and rate limit configuration for TOTP actions.
**3. Audit Log**
```elixir
brute_force_strategy {:audit_log, :my_audit_log}
```
Requires an audit log add-on that logs TOTP actions.
## Two-Step Setup with Confirmation (Recommended)
For better security, use two-step setup. This ensures users have correctly saved
their secret before it's activated:
```elixir
authentication do
tokens do
enabled? true
token_resource MyApp.Accounts.Token
end
strategies do
totp do
identity_field :email
confirm_setup_enabled? true
setup_token_lifetime {10, :minutes}
brute_force_strategy {:preparation, MyApp.TotpBruteForcePreparation}
end
end
end
```
This changes the flow:
1. **Setup** - `setup_with_totp` returns a `setup_token` and `totp_url` in metadata
(secret is NOT stored on user yet)
2. **Display QR Code** - Show the QR code to the user
3. **Confirm** - User enters a code, call `confirm_setup_with_totp` with the token and code
4. **Activation** - If code is valid, secret is stored on user
### Example Setup Flow
```elixir
# Step 1: Initiate setup
{:ok, user} = Ash.update(user, action: :setup_with_totp)
setup_token = user.__metadata__.setup_token
totp_url = user.__metadata__.totp_url
# Step 2: Display QR code (use totp_url with a QR code library)
# The URL format is: otpauth://totp/Issuer:user@example.com?secret=BASE32SECRET&issuer=Issuer
# Step 3: User scans QR code and enters the code from their app
{:ok, user} = Ash.update(user,
action: :confirm_setup_with_totp,
params: %{setup_token: setup_token, code: "123456"}
)
# User now has TOTP enabled
```
## Standalone TOTP Sign-In
To use TOTP as a primary authentication method:
```elixir
authentication do
strategies do
totp do
identity_field :email
sign_in_enabled? true
brute_force_strategy {:preparation, MyApp.TotpBruteForcePreparation}
end
end
end
```
This generates a `sign_in_with_totp` action that takes an identity and code,
returning an authenticated user with a token.
## Verifying TOTP Codes
The `verify_with_totp` action checks if a code is valid without signing in.
This is useful for 2FA flows where you want to verify the code as a second step:
```elixir
# After password authentication, verify TOTP
strategy = AshAuthentication.Info.strategy!(MyApp.Accounts.User, :totp)
{:ok, true} = AshAuthentication.Strategy.action(strategy, :verify, %{
user: user,
code: "123456"
})
```
## Generating QR Codes
The `totp_url_for_totp` calculation generates the standard `otpauth://` URL:
```elixir
user = Ash.load!(user, :totp_url_for_totp)
qr_url = user.totp_url_for_totp
# => "otpauth://totp/MyApp:user@example.com?secret=JBSWY3DPEHPK3PXP&issuer=MyApp"
```
Use a QR code library to render this URL:
```elixir
# With eqrcode
qr_code = EQRCode.encode(qr_url)
svg = EQRCode.svg(qr_code)
```
## Configuration Options
| Option | Default | Description |
|--------|---------|-------------|
| `identity_field` | `:username` | Field that identifies users (e.g., `:email`) |
| `secret_field` | `:totp_secret` | Attribute storing the TOTP secret |
| `last_totp_at_field` | `:last_totp_at` | Attribute tracking last successful auth |
| `issuer` | Strategy name | Displayed in authenticator apps |
| `period` | `30` | Code validity period in seconds (recommended: 15-300) |
| `secret_length` | `20` | Secret length in bytes (recommended: 16+, per RFC 4226) |
| `setup_enabled?` | `true` | Generate setup action |
| `sign_in_enabled?` | `false` | Generate sign-in action |
| `verify_enabled?` | `true` | Generate verify action |
| `confirm_setup_enabled?` | `false` | Use two-step setup flow (requires `setup_enabled?`) |
| `setup_token_lifetime` | `{10, :minutes}` | How long setup tokens are valid |
## Security Considerations
1. **Always use brute force protection** - TOTP codes are only 6 digits
2. **Use confirm_setup_enabled?** - Ensures users correctly saved their secret
3. **Store secrets securely** - Mark the secret field as `sensitive?: true`
4. **Track last_totp_at** - Prevents replay attacks within the same time window
5. **Provide recovery codes** - Use the [recovery code strategy](/documentation/tutorials/recovery-codes.md) to give users backup codes for when they lose access to their authenticator app