Files
Mixer/.agents/skills/ash-framework/references/ash_authentication.md

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_password attributes, unique identity

Magic Link - Passwordless email authentication

  • Requires: :email attribute, 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
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?: false for internal fields and highly sensitive PII
  • Use public?: true for identity fields and UI display data
  • Include required authentication changes (GenerateTokenChange, HashPasswordChange, etc.)

Argument Handling:

  • All arguments must be used in accept or change set_attribute()
  • Use allow_nil?: false for 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.