# 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

```elixir
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

```elixir
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

```elixir
# 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
```elixir
# 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
```elixir
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
```elixir
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
```elixir
# 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
```elixir
# 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
```elixir
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:

```elixir
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:

```elixir
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:

```elixir
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:**
```elixir
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.