AshAuthentication.Strategy.RememberMe (ash_authentication v4.13.3)

View Source

Strategy for authenticating using a remember me token that has a configurable token_lifetime and is typically valid longer than a session token. Remember me tokens are generated by other strategies (e.g. MagicLink) to allow for authentication to continue beyond the scope of the current session.

In order to use remember me authentication you need to have another strategy enabled that supports remember me.

Example Usage

Add the Remember Me strategy to your authenticated resource and update another strategy to the generate the remember_me token.

defmodule MyApp.Accounts.User do
  use Ash.Resource,
    extensions: [AshAuthentication],
    domain: MyApp.Accounts

  attributes do
    uuid_primary_key :id
    attribute :email, :ci_string, allow_nil?: false
  end

  authentication do
    # Tokens are required for RememberMe
    tokens do
      enabled? true
      store_all_tokens? true
      require_token_presence_for_authentication? true
    end

    # Make sure you use the log_out_everywhere add on
    # to revoke remember me tokens on password change otherwise
    # the remember me tokens will continue to sign in users
    add_ons do
      log_out_everywhere do
        apply_on_password_change? true
      end
    end

    strategies do
      password do
        identity_field :email
        hashed_password_field :hashed_password
        hash_provider AshAuthentication.BcryptProvider
        confirmation_required? true
      end

      # Add the remember me Strategy
      remember_me :remember_me do
        sign_in_action_name :sign_in_with_remember_me # Optional defaults to :sign_in_with_[:strategy_name]
        cookie_name :remember_me # Optional. Defaults to :remember_me
        token_lifetime {30, :days} # Optional. Defaults to {30, :days}
      end
    end
  end

  # In any of the actions used by your other strategies...
  actions do
    read :sign_in_with_password do
      ...
      # Add an argument to your form
      argument :remember_me, :boolean do
        description "Whether to generate a remember me token."
        allow_nil? true
      end

      # Add the preparation that generates the token
      prepare {AshAuthentication.Strategy.RememberMe.MaybeGenerateTokenPreparation, strategy_name: :remember_me}

      # Add the metadata map that will contain the cookie_name, token, and other values
      # after a successful sign in.
      metadata :remember_me, :map do
        description "A map that includes the token options"
        allow_nil? true
      end
    end

    read :sign_in_with_remember_me do
      description "Attempt to sign in using a remember me token."
      get? true

      argument :token, :string do
        description "The remember me token"
        allow_nil? false
        sensitive? true
      end

      # validates the provided the remember me token and generates a token for the session
      prepare AshAuthentication.Strategy.RememberMe.SignInPreparation

      metadata :token, :string do
        description "A JWT that can be used to authenticate the user."
        allow_nil? false
      end
    end
  end
end

When the user successfully signs in, the user.metadata.remember_me field will contain a map with the token and max_age.

For create actions, such as registering a new user and signing them in immediately, use AshAuthentication.Strategy.RememberMe.MaybeGenerateTokenChange. This serves as an alternative to the preparation used for read actions and mirrors the behavior of the Magic Link strategy.

If you're using AshAuthentication.Phoenix, update your AuthController to store the cookie with a put_remember_me_cookie/3 callback. Other callbacks include delete_remember_me_cookie/2, and delete_all_remember_me_cookies/1

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

  @impl AshAuthentication.Phoenix.Controller
  def put_remember_me_cookie(conn, cookie_name, %{token: token, max_age: max_age}) do
    cookie_options = [
      max_age: max_age, # matches the token lifetime
      http_only: true, # prevents the cookie from being accessed by JavaScript
      secure: true, # only send the cookie over HTTPS
      same_site: "lax" # prevents the cookie from being sent with cross-site requests
    ]
    conn
    |> put_resp_cookie(cookie_name, token, cookie_options)
  end
end

Update your router to sign in the user with the remember me token.

defmodule MyAppWeb.Router do
  pipeline :browser do
    ...
    plug :sign_in_with_remember_me # make sure this comes before load_from_session
    plug :load_from_session
  end
end

You should also delete the remember me token on sign out to prevent the user from immediately being signed back in. This will sign them out of their current session, but it will not revoke remember me tokens on other devices.

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

  @impl AshAuthentication.Phoenix.Controller
  def sign_out(conn, _params) do
    return_to = get_session(conn, :return_to) || ~p"/"

    conn
    |> clear_session(:my_otp_app)
    |> AshAuthentication.Strategy.RememberMe.Plug.Helpers.delete_all_remember_me_cookies(:my_otp_app)
    |> put_flash(:info, "You are now signed out")
    |> redirect(to: return_to)
  end
end

Summary

Types

t()

@type t() :: %AshAuthentication.Strategy.RememberMe{
  __spark_metadata__: Spark.Dsl.Entity.spark_meta(),
  cookie_name: atom(),
  identity_field: atom(),
  name: atom(),
  registration_enabled?: boolean(),
  resource: module(),
  sign_in_action_name: :atom,
  token_lifetime: pos_integer()
}

Functions

transform(entity, dsl_state)

Callback implementation for AshAuthentication.Strategy.Custom.transform/2.

verify(strategy, dsl_state)

Callback implementation for AshAuthentication.Strategy.Custom.verify/2.