9.1 KiB
9.1 KiB
AshAuthentication Usage Rules
Core Concepts
- Strategies: password, OAuth2, magic_link, api_key authentication methods
- Tokens: JWT for stateless authentication
- UserIdentity: links users to OAuth2 providers
- Add-ons: confirmation, logout-everywhere functionality
- Actions: auto-generated by strategies (register, sign_in, etc.), can be overridden on the resource
Key Principles
- Always use secrets management - never hardcode credentials
- Enable tokens for magic_link, confirmation, OAuth2
- UserIdentity resource optional for OAuth2 (required for multiple providers per user)
- API keys require strict policy controls and expiration management
- Use prefixes for API keys to enable secret scanning compliance
- Check existing strategies:
AshAuthentication.Info.strategies/1
Strategy Selection
Password - Email/password authentication
- Requires:
:email,:hashed_passwordattributes, unique identity
Magic Link - Passwordless email authentication
- Requires:
:emailattribute, sender implementation, tokens enabled
API Key - Token-based authentication for APIs
- Requires: API key resource, relationship to user, sign-in action
OAuth2 - Social/enterprise login (GitHub, Google, Auth0, Apple, OIDC, Slack)
- Requires: custom actions, secrets
- Optional: UserIdentity resource (for multiple providers per user)
Password Strategy
authentication do
strategies do
password :password do
identity_field :email
hashed_password_field :hashed_password
resettable do
sender MyApp.PasswordResetSender
end
end
end
end
# Required attributes:
attributes do
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
end
identities do
identity :unique_email, [:email]
end
Magic Link Strategy
authentication do
strategies do
magic_link do
identity_field :email
sender MyApp.MagicLinkSender
end
end
end
# Sender implementation required:
defmodule MyApp.MagicLinkSender do
use AshAuthentication.Sender
def send(user_or_email, token, _opts) do
MyApp.Emails.deliver_magic_link(user_or_email, token)
end
end
API Key Strategy
# 1. Create API key resource
defmodule MyApp.Accounts.ApiKey do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
authorizers: [Ash.Policy.Authorizer]
actions do
defaults [:read, :destroy]
create :create do
primary? true
accept [:user_id, :expires_at]
change {AshAuthentication.Strategy.ApiKey.GenerateApiKey, prefix: :myapp, hash: :api_key_hash}
end
end
attributes do
uuid_primary_key :id
attribute :api_key_hash, :binary, allow_nil?: false, sensitive?: true
attribute :expires_at, :utc_datetime_usec, allow_nil?: false
end
relationships do
belongs_to :user, MyApp.Accounts.User, allow_nil?: false
end
calculations do
calculate :valid, :boolean, expr(expires_at > now())
end
identities do
identity :unique_api_key, [:api_key_hash]
end
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
end
end
# 2. Add strategy to user resource
authentication do
strategies do
api_key do
api_key_relationship :valid_api_keys
api_key_hash_attribute :api_key_hash
end
end
end
# 3. Add relationship to user
relationships do
has_many :valid_api_keys, MyApp.Accounts.ApiKey do
filter expr(valid)
end
end
# 4. Add sign-in action to user
actions do
read :sign_in_with_api_key do
argument :api_key, :string, allow_nil?: false
prepare AshAuthentication.Strategy.ApiKey.SignInPreparation
end
end
Security considerations:
- API keys are hashed for storage security
- Use policies to restrict API key access to specific actions
- Check
user.__metadata__[:using_api_key?]to detect API key authentication - Access the API key via
user.__metadata__[:api_key]for permission checks
OAuth2 Strategies
Supported providers: github, google, auth0, apple, oidc, slack
Required for all OAuth2:
- Custom
register_with_[provider]action - Secrets management
- Tokens enabled
Optional for all OAuth2:
- UserIdentity resource (for multiple providers per user)
OAuth2 Configuration Pattern
# Strategy configuration
authentication do
strategies do
github do # or google, auth0, apple, oidc, slack
client_id MyApp.Secrets
client_secret MyApp.Secrets
redirect_uri MyApp.Secrets
# auth0 also needs: base_url
# apple also needs: team_id, private_key_id, private_key_path
# oidc also needs: openid_configuration_uri
identity_resource MyApp.Accounts.UserIdentity
end
end
end
# Required action (replace 'github' with provider name)
actions do
create :register_with_github do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :unique_email
change AshAuthentication.GenerateTokenChange
# If UserIdentity resource is being used
change AshAuthentication.Strategy.OAuth2.IdentityChange
change fn changeset, _ctx ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
Ash.Changeset.change_attributes(changeset, Map.take(user_info, ["email"]))
end
end
end
Add-ons
Confirmation
authentication do
tokens do
enabled? true
token_resource MyApp.Accounts.Token
end
add_ons do
confirmation :confirm do
monitor_fields [:email]
sender MyApp.ConfirmationSender
end
end
end
Log Out Everywhere
authentication do
tokens do
store_all_tokens? true
end
add_ons do
log_out_everywhere do
apply_on_password_change? true
end
end
end
Working with Authentication
Strategy Protocol
# Get and use strategies
strategy = AshAuthentication.Info.strategy!(MyApp.User, :password)
{:ok, user} = AshAuthentication.Strategy.action(strategy, :sign_in, params)
# List strategies
strategies = AshAuthentication.Info.strategies(MyApp.User)
Token Operations
# User/subject conversion
subject = AshAuthentication.user_to_subject(user)
{:ok, user} = AshAuthentication.subject_to_user(subject, MyApp.User)
# Token management
AshAuthentication.TokenResource.revoke(MyApp.Token, token)
Policies
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
end
Common Implementation Patterns
Pattern: Multiple Authentication Methods
When users need multiple ways to authenticate:
authentication do
tokens do
enabled? true
token_resource MyApp.Accounts.Token
end
strategies do
password :password do
identity_field :email
hashed_password_field :hashed_password
end
github do
client_id MyApp.Secrets
client_secret MyApp.Secrets
redirect_uri MyApp.Secrets
identity_resource MyApp.Accounts.UserIdentity
end
magic_link do
identity_field :email
sender MyApp.MagicLinkSender
end
end
end
Pattern: OAuth2 with User Registration
When new users can register via OAuth2:
actions do
create :register_with_github do
argument :user_info, :map, allow_nil?: false
argument :oauth_tokens, :map, allow_nil?: false
upsert? true
upsert_identity :email
change AshAuthentication.GenerateTokenChange
change fn changeset, _ctx ->
user_info = Ash.Changeset.get_argument(changeset, :user_info)
changeset
|> Ash.Changeset.change_attribute(:email, user_info["email"])
|> Ash.Changeset.change_attribute(:name, user_info["name"])
end
end
end
Pattern: Custom Token Configuration
When you need specific token behavior:
authentication do
tokens do
enabled? true
token_resource MyApp.Accounts.Token
signing_secret MyApp.Secrets
token_lifetime {24, :hours}
store_all_tokens? true # For logout-everywhere functionality
require_token_presence_for_authentication? false
end
end
Customizing Authentication Actions
When customizing generated authentication actions (register, sign_in, etc.):
Key Security Rules:
- Always mark credentials with
sensitive?: true(passwords, API keys, tokens) - Use
public?: falsefor internal fields and highly sensitive PII - Use
public?: truefor identity fields and UI display data - Include required authentication changes (
GenerateTokenChange,HashPasswordChange, etc.)
Argument Handling:
- All arguments must be used in
acceptorchange set_attribute() - Use
allow_nil?: falsefor required arguments - OAuth2 data must be extracted in changes, not accepted directly
Example Custom Registration:
create :register_with_password do
argument :password, :string, allow_nil?: false, sensitive?: true
argument :first_name, :string, allow_nil?: false
accept [:email, :first_name]
change AshAuthentication.GenerateTokenChange
change AshAuthentication.Strategy.Password.HashPasswordChange
end
For more guidance, see the "Customizing Authentication Actions" section in the getting started guide.