documentation/topics/auto-signout.md

<!--
SPDX-FileCopyrightText: 2022 Alembic Pty Ltd

SPDX-License-Identifier: MIT
-->

# Auto Sign-out

Auto sign-out automatically disconnects LiveView sessions when a user's tokens are revoked. This ensures that when a user signs out (or triggers "sign out everywhere"), any active LiveView sessions are immediately disconnected rather than remaining active until the next page refresh.

## When Auto Sign-out Triggers

Auto sign-out is triggered whenever tokens are revoked, which happens:

- When a user explicitly signs out
- When the `log_out_everywhere` add-on revokes all tokens for a user (e.g., after a password change)
- When tokens are manually revoked via `AshAuthentication.TokenResource.revoke/3`

## Prerequisites

1. **Tokens must be enabled** in your authentication configuration
2. **A TokenResource** must be configured
3. **AshAuthentication.Phoenix** must be installed (for the notifier and helpers)
4. The `log_out_everywhere` add-on is recommended for password change scenarios

## Configuration

### Step 1: Configure TokenResource (AshAuthentication)

Add the `endpoints` and `live_socket_id_template` options to your token resource:

```elixir
defmodule MyApp.Accounts.Token do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.TokenResource],
    domain: MyApp.Accounts

  postgres do
    table "tokens"
    repo MyApp.Repo
  end

  token do
    endpoints [MyAppWeb.Endpoint]
    live_socket_id_template fn %{jti: jti} -> "users_sessions:#{jti}" end
  end
end
```

- `endpoints` - List of Phoenix endpoints to notify when tokens are revoked
- `live_socket_id_template` - Function that generates the live socket ID from a map containing `%{jti: jti}`. Additional keys may be added in future versions.

### Step 2: Add the Notifier (AshAuthentication.Phoenix)

Add `AshAuthentication.Phoenix.TokenRevocationNotifier` to your token resource's notifiers:

```elixir
defmodule MyApp.Accounts.Token do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.TokenResource],
    domain: MyApp.Accounts,
    notifiers: [AshAuthentication.Phoenix.TokenRevocationNotifier]

  # ... rest of configuration
end
```

The notifier broadcasts disconnect messages through your configured endpoints when tokens are revoked.

### Step 3: Set Live Socket ID on Sign-in (AshAuthentication.Phoenix)

In your authentication controller, call `set_live_socket_id/2` after successful sign-in to store the socket ID in the session:

```elixir
defmodule MyAppWeb.AuthController do
  use MyAppWeb, :controller
  use AshAuthentication.Phoenix.Controller

  def success(conn, _activity, user, _token) do
    conn
    |> set_live_socket_id(user)
    |> store_in_session(user)
    |> assign(:current_user, user)
    |> redirect(to: ~p"/")
  end

  def failure(conn, _activity, _reason) do
    conn
    |> put_flash(:error, "Authentication failed")
    |> redirect(to: ~p"/sign-in")
  end

  def sign_out(conn, _params) do
    conn
    |> clear_session()
    |> redirect(to: ~p"/")
  end
end
```

## How It Works

1. When a user signs in, `set_live_socket_id/2` stores the live socket ID (generated from the token's JTI) in the session
2. LiveView uses this socket ID to identify the connection
3. When a token is revoked, the `TokenRevocationNotifier` uses the `live_socket_id_template` function to generate the same socket ID from the revoked token's JTI
4. The notifier broadcasts a disconnect message through the configured endpoints
5. LiveView receives the disconnect and terminates the session

## Complete Example

### Token Resource

```elixir
defmodule MyApp.Accounts.Token do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication.TokenResource],
    domain: MyApp.Accounts,
    notifiers: [AshAuthentication.Phoenix.TokenRevocationNotifier]

  postgres do
    table "tokens"
    repo MyApp.Repo
  end

  token do
    endpoints [MyAppWeb.Endpoint]
    live_socket_id_template fn %{jti: jti} -> "users_sessions:#{jti}" end
  end
end
```

### User Resource with Log Out Everywhere

```elixir
defmodule MyApp.Accounts.User do
  use Ash.Resource,
    data_layer: AshPostgres.DataLayer,
    extensions: [AshAuthentication],
    domain: MyApp.Accounts

  authentication do
    tokens do
      enabled? true
      token_resource MyApp.Accounts.Token
      store_all_tokens? true
    end

    strategies do
      password :password do
        identity_field :email
      end
    end

    add_ons do
      log_out_everywhere do
        apply_on_password_change? true
      end
    end
  end

  # ... attributes, identities, etc.
end
```

### Auth Controller

```elixir
defmodule MyAppWeb.AuthController do
  use MyAppWeb, :controller
  use AshAuthentication.Phoenix.Controller

  def success(conn, _activity, user, _token) do
    conn
    |> set_live_socket_id(user)
    |> store_in_session(user)
    |> assign(:current_user, user)
    |> redirect(to: ~p"/")
  end

  def failure(conn, _activity, _reason) do
    conn
    |> put_flash(:error, "Authentication failed")
    |> redirect(to: ~p"/sign-in")
  end

  def sign_out(conn, _params) do
    conn
    |> clear_session()
    |> redirect(to: ~p"/")
  end
end
```