ash-authentication

forest's avatarfrom forest

AshAuthentication guidelines for implementing authentication in Ash Framework. Use when adding password, magic link, API key, or OAuth2 authentication strategies. Covers token configuration, UserIdentity resources, confirmation add-ons, and customizing authentication actions. Never hardcode credentials.

⭐4stars🔀0forks📁View on GitHub🕐Updated Dec 24, 2025

When & Why to Use This Skill

This Claude skill provides comprehensive guidelines and standardized code patterns for implementing robust authentication systems within the Ash Framework. It streamlines the integration of diverse strategies including password-based login, magic links, API keys, and multi-provider OAuth2, while enforcing security best practices like credential encryption and token management.

Use Cases

  • Implementing secure password-based authentication with built-in hashing, sensitive field protection, and password reset workflows.
  • Integrating third-party social logins (OAuth2) such as GitHub, Google, and Apple into Elixir applications using AshAuthentication.
  • Developing stateless API authentication layers using JWT tokens and hashed API keys for secure machine-to-machine communication.
  • Configuring advanced user lifecycle features including email confirmation, magic link sign-ins, and 'log out everywhere' functionality.
nameash-authentication
descriptionAshAuthentication guidelines for implementing authentication in Ash Framework. Use when adding password, magic link, API key, or OAuth2 authentication strategies. Covers token configuration, UserIdentity resources, confirmation add-ons, and customizing authentication actions. Never hardcode credentials.

AshAuthentication Guidelines

Core Concepts

  • Strategies: password, magic_link, api_key, OAuth2 (github, google, auth0, apple, oidc, slack)
  • Tokens: JWT for stateless authentication
  • UserIdentity: Links users to OAuth2 providers (optional, required for multiple providers per user)
  • Add-ons: confirmation, logout-everywhere

Key Principles

  • Never hardcode credentials - always use secrets management
  • Enable tokens for magic_link, confirmation, OAuth2
  • Check existing strategies: AshAuthentication.Info.strategies(MyApp.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

API Key Security:

  • Keys are hashed for storage
  • Check user.__metadata__[:using_api_key?] for API key auth detection
  • Access key via user.__metadata__[:api_key] for permission checks
  • Use prefixes for secret scanning compliance

OAuth2 Strategies

Providers: github, google, auth0, apple, oidc, slack

authentication do
  strategies do
    github do
      client_id MyApp.Secrets
      client_secret MyApp.Secrets
      redirect_uri MyApp.Secrets
      identity_resource MyApp.Accounts.UserIdentity  # Optional
    end
  end
end

# Required action (replace 'github' with provider)
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
    change AshAuthentication.Strategy.OAuth2.IdentityChange  # If using UserIdentity

    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

Provider-specific requirements:

  • auth0: also needs base_url
  • apple: also needs team_id, private_key_id, private_key_path
  • oidc: also needs openid_configuration_uri

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

Token Configuration

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
    require_token_presence_for_authentication? false
  end
end

Strategy Protocol

# Get and use strategies
strategy = AshAuthentication.Info.strategy!(MyApp.User, :password)
{:ok, user} = AshAuthentication.Strategy.action(strategy, :sign_in, params)

# Token operations
subject = AshAuthentication.user_to_subject(user)
{:ok, user} = AshAuthentication.subject_to_user(subject, MyApp.User)
AshAuthentication.TokenResource.revoke(MyApp.Token, token)

Customizing Authentication Actions

Security Rules:

  • Mark credentials with sensitive?: true (passwords, API keys, tokens)
  • Use public?: false for internal fields and sensitive PII
  • Use public?: true for identity fields and UI display data
  • Include required changes (GenerateTokenChange, HashPasswordChange)
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

Policies

Always bypass for authentication interactions:

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