{"id":32173828,"url":"https://github.com/curator-ex/curator","last_synced_at":"2026-02-21T03:31:28.142Z","repository":{"id":57487765,"uuid":"82466631","full_name":"curator-ex/curator","owner":"curator-ex","description":"An Authentication and User Lifecycle Framework for Phoenix","archived":false,"fork":false,"pushed_at":"2020-07-17T16:45:09.000Z","size":263,"stargazers_count":24,"open_issues_count":4,"forks_count":1,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-09-16T18:56:24.408Z","etag":null,"topics":["authentication","phoenix"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/curator-ex.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-02-19T14:56:09.000Z","updated_at":"2023-09-01T08:51:53.000Z","dependencies_parsed_at":"2022-08-29T11:20:53.774Z","dependency_job_id":null,"html_url":"https://github.com/curator-ex/curator","commit_stats":null,"previous_names":[],"tags_count":14,"template":false,"template_full_name":null,"purl":"pkg:github/curator-ex/curator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curator-ex%2Fcurator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curator-ex%2Fcurator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curator-ex%2Fcurator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curator-ex%2Fcurator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/curator-ex","download_url":"https://codeload.github.com/curator-ex/curator/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/curator-ex%2Fcurator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29672704,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-21T03:11:15.450Z","status":"ssl_error","status_checked_at":"2026-02-21T03:10:34.920Z","response_time":107,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["authentication","phoenix"],"created_at":"2025-10-21T19:00:45.480Z","updated_at":"2026-02-21T03:31:28.133Z","avatar_url":"https://github.com/curator-ex.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Curator\n\nAn Authentication Framework for Phoenix.\n\nCurator is meant to mimic [Devise](https://github.com/plataformatec/devise), as such it provides [several modules](#curator-modules) to accomplish authentication and various aspects of user lifecycle mangement. It's build with a modular architecture that differs from existing Elixir Authentication solutions. Each curator module can be combined to handle various authentication scenarios, passing coordination through a [curator module](#curator-module). Under the hood, this uses [Guardian](https://github.com/ueberauth/guardian) for session management.\n\nFor an example, see the [PhoenixCurator Application](https://github.com/curator-ex/phoenix_curator)\n\n## Curator Modules\n\n* [Ueberauth](#ueberauth): Ueberauth Integration.\n* [Database Authenticatable](#database-authenticatable): Compare a password to a hashed password to support password based sign-in. Also provide a generator for creating a session page.\n* [Registerable](#registerable): A Generator to support user registration.\n* [Confirmable](#confirmable): Account email verification.\n* [Recoverable](#recoverable): Reset the User Password.\n* [Lockable](#lockable): Lock Account after configurable count of invalid sign-ins.\n* [Approvable](#approvable): Require an approval step before user sign-in.\n* [Timeoutable](#timeoutable): Session Timeout (after configurable inactivity).\n* [API](#api): API login (with an opaque token).\n\n## Installation\n\n1. Add `curator` to your list of dependencies in `mix.exs`:\n\n    ```elixir\n    def deps do\n      [{:curator, \"~\u003e 0.3.0\"}]\n    end\n    ```\n\n2. Run the install command\n\n    ```\n    mix curator.install\n    ```\n\n    This will generate:\n\n    1. A User context, migration, and schema (in the Ecto application if an umbrella)\n\n        * A user migration (`priv/repo/migrations/\u003ctimestamp\u003e_create_users.exs`)\n        * A user schema (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/user.ex`)\n        * A user context (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/auth.ex`)\n\n    2. An empty Curator module (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    3. A Guardian Configuration\n\n        * A Guardian module (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/guardian.ex`)\n        * An error handler  (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/controllers/auth/error_handler.ex`)\n\n    4. A view helper (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/views/auth/curator_helper.ex`)\n\n    5. A Session Controller\n\n        * controller (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/controllers/auth/session_controller.ex`)\n        * view helper (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/views/auth/curator_helper.ex`)\n        * new template (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/templates/auth/session/new.html.eex`). Note: this is just a placeholder that you'll want to update when you decide on a sign-in strategy.\n\n3. The generators aren't perfect (TODO), so finish the installation\n\n    1. Update your router (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/router.ex`)\n\n        ```elixir\n        require Curator.Router\n\n        pipeline :browser do\n          ...\n          plug \u003cMyAppWeb\u003e.Auth.Curator.UnauthenticatedPipeline\n        end\n\n        pipeline :authenticated_browser do\n          ... (copy the code from browser)\n          plug \u003cMyAppWeb\u003e.Auth.Curator.AuthenticatedPipeline\n        end\n\n        scope \"/\", \u003cMyAppWeb\u003e do\n          pipe_through :browser\n\n          ...\n          Insert your unprotected routes here\n          ...\n\n          Curator.Router.mount_unauthenticated_routes(\u003cMyAppWeb\u003e.Auth.Curator)\n        end\n\n        scope \"/\", \u003cMyAppWeb\u003e do\n          pipe_through :authenticated_browser\n\n          ...\n          Insert your protected routes here\n          ...\n\n          Curator.Router.mount_authenticated_routes(\u003cMyAppWeb\u003e.Auth.Curator)\n        end\n        ```\n\n    2. Add the view_helper to your web module (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e.ex`)\n\n        ```elixir\n        def view do\n          quote do\n            ...\n\n            import \u003cMyAppWeb\u003e.Auth.CuratorHelper\n          end\n        end\n        ```\n\n        This allows you to call `current_user(@conn)` in your templates\n\n    3. [Configure Guardian](https://github.com/ueberauth/guardian#installation) in `config.exs`\n\n        ```elixir\n        config :\u003cmy_app_web\u003e, \u003cMyAppWeb\u003e.Auth.Guardian,\n          issuer: \"\u003cmy_app_web\u003e\",\n          secret_key: \"Secret key. You can use `mix guardian.gen.secret` to get one\"\n        ```\n\n        and `prod.exs`\n\n        ```elixir\n        config :\u003cmy_app_web\u003e, \u003cMyAppWeb\u003e.Auth.Guardian,\n          issuer: \"\u003cmy_app_web\u003e\",\n          allowed_algos: [\"HS512\"],\n          ttl: { 1, :days },\n          verify_issuer: true,\n          secret_key: {\u003cMyAppWeb\u003e.Auth.Guardian, :fetch_secret_key, []}\n        ```\n\n        (NOTE: the sample prod.exs is one way to keep the `secret_key` out of source code. If you use an alternative technique the `fetch_secret_key` method can be removed from `\u003cMyAppWeb\u003e.Auth.Guardian`)\n\n4. Add a sign-out link to your layout\n\n    ```elixir\n    \u003c%= if current_user(@conn) do %\u003e\n      \u003c%= link \"Sign Out\", to: Routes.session_path(@conn, :delete), method: :delete %\u003e\n    \u003c% end %\u003e\n    ```\n\n5. Testing\n\n    Update `conn_case.ex`:\n\n    ```elixir\n    setup tags do\n      :ok = Ecto.Adapters.SQL.Sandbox.checkout(\u003cMyApp\u003e.Repo)\n      unless tags[:async] do\n        Ecto.Adapters.SQL.Sandbox.mode(\u003cMyApp\u003e.Repo, {:shared, self()})\n      end\n\n      # Note: As you add additional modules, make sure this user is valid for them too.\n      # Create the user w/ ExMachina  \n      auth_user = \u003cMyApp\u003e.Factory.insert(:auth_user)\n\n      # Or, create the user with your preferred method\n      # {:ok, auth_user} = \u003cMyApp\u003e.Auth.create_user(%{email: \"test@test.com\"})\n\n      {:ok, token, _claims} = \u003cMyAppWeb\u003e.Auth.Guardian.encode_and_sign(auth_user)\n\n      conn = Phoenix.ConnTest.build_conn()\n\n      auth_conn = conn\n      |\u003e Plug.Test.init_test_session(%{\n        guardian_default_token: token,\n        guardian_default_timeoutable: Curator.Time.timestamp(),\n      })\n\n      {:ok, unauth_conn: conn, auth_user: auth_user, conn: auth_conn}\n    end\n    ```\n\n    Note: This uses `conn` as an authenticated connection, so existing tests won't need to be updated.\n\n    To test, create test-only routes:\n\n    ```elixir\n    scope \"/\", \u003cMyAppWeb\u003e do\n      pipe_through :browser\n\n      if Mix.env == :test do\n        get \"/insecure\", PageController, :insecure\n      end\n\n      Curator.Router.mount_unauthenticated_routes(\u003cMyAppWeb\u003e.Auth.Curator)\n    end\n\n    scope \"/\", \u003cMyAppWeb\u003e do\n      pipe_through :authenticated_browser\n\n      if Mix.env == :test do\n        get \"/secure\", PageController, :secure\n      end\n\n      Curator.Router.mount_authenticated_routes(\u003cMyAppWeb\u003e.Auth.Curator)\n    end\n    ```\n\n    Update the `page_controller.ex`:\n\n    ```elixir\n    defmodule \u003cMyAppWeb\u003e.PageController do\n      use \u003cMyAppWeb\u003e, :controller\n\n      def secure(conn, _params) do\n        text conn, \"!!!SECURE!!!\"\n      end\n\n      def insecure(conn, _params) do\n        text conn, \"INSECURE\"\n      end\n    end\n    ```\n\n    And write tests in `page_controller_test.exs`:\n\n    ```\n    defmodule \u003cMyAppWeb\u003e.PageControllerTest do\n      use \u003cMyAppWeb\u003e.ConnCase\n\n      test \"GET /secure (Unauthenticated)\", %{unauth_conn: conn} do\n        conn = get conn, \"/secure\"\n        assert redirected_to(conn) == Routes.session_path(conn, :new)\n        assert get_flash(conn, :error) == \"Please Sign In\"\n      end\n\n      test \"GET /secure (Authenticated)\", %{conn: conn} do\n        conn = get conn, \"/secure\"\n        assert text_response(conn, 200) == \"!!!SECURE!!!\"\n      end\n\n      test \"GET /secure (Authenticated - User Delete)\", %{conn: conn, auth_user: user} do\n        \u003cMyApp\u003e.Auth.delete_user(user)\n        conn = get conn, \"/secure\"\n        assert redirected_to(conn) == Routes.session_path(conn, :new)\n        assert get_flash(conn, :error) == \"Please Sign In\"\n      end\n\n      test \"GET /insecure (Unauthenticated)\", %{unauth_conn: conn} do\n        conn = get conn, \"/insecure\"\n        assert text_response(conn, 200) == \"INSECURE\"\n      end\n\n      test \"GET /insecure (Authenticated)\", %{conn: conn} do\n        conn = get conn, \"/insecure\"\n        assert text_response(conn, 200) == \"INSECURE\"\n      end\n\n      test \"GET /insecure (Authenticated - User Delete)\", %{conn: conn, auth_user: user} do\n        \u003cMyApp\u003e.Auth.delete_user(user)\n        conn = get conn, \"/insecure\"\n        assert redirected_to(conn) == Routes.session_path(conn, :new)\n        assert get_flash(conn, :error) == \"Please Sign In\"\n      end\n    end\n    ```\n\n    These examples can be extended as additional modules are integrated (ex. using Confirmable with a user that hasn't been confirmed).\n\n6. Opaque Guardian (recommended)\n\n    Several Extensions will use guardian to store single-use tokens (ex: the confirmation module). By Default, [JWT Tokens are not tracked by the application](https://github.com/ueberauth/guardian#tracking-tokens). Guardian provides an integration to track and revoke tokens, [GuardianDB](https://github.com/ueberauth/guardian_db). However, for single-use tokens, Curator ships with an alternative called \"Opaque Guardian\". It provides a behaviour for guardian that allows tokens to be stored in the Database and never as a JWT. This is preferable for the extensions. Installation requires running the following command (or typing yes during the install):\n\n    ```\n    mix curator.opaque_guardian.install\n    ```\n\n    If you would like to use guardian for the single-use tokens you can update the curator configuration:\n\n    ```\n    use Curator,\n      opaque_guardian: \u003cMyAppWeb\u003e.Auth.Guardian\n    ```\n\n7. Curate.\n\n    Your authentication library is looking a bit spartan... Time to add to your collection.\n\n    To allow sign in add [Ueberauth](#ueberauth) and/or [DatabaseAuthenticatable](#database_authenticatable)\n\n## Module Documentation\n\n### Ueberauth\n\n#### Description\nUeberauth Integration\n\n#### Installation\n\n1. Run the install command\n\n    ```\n    mix curator.ueberauth.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator, otp_app: :my_app_web,\n      modules: [\n        \u003cMyAppWeb\u003e.Auth.Ueberauth,\n      ]\n    ```\n\n3. Install Ueberauth and the desired [strategies](https://github.com/ueberauth/ueberauth#configuring-providers). For example, to add google oauth:\n\n    1. Update `mix.exs`\n\n        ```elixir\n        defp deps do\n          [\n            {:ueberauth_google, \"~\u003e 0.9\"},\n            {:ueberauth, \"~\u003e 0.6\"}\n          ]\n        end\n        ```\n\n        NOTE: If you're using an umbrella app you'll also need to add ueberauth to your ecto applications `mix.exs`.\n\n    2. Update `config.exs`\n\n        ```elixir\n        config :ueberauth, Ueberauth,\n          providers: [\n            google: {Ueberauth.Strategy.Google, []}\n          ]\n\n        config :ueberauth, Ueberauth.Strategy.Google.OAuth,\n          client_id: System.get_env(\"GOOGLE_CLIENT_ID\"),\n          client_secret: System.get_env(\"GOOGLE_CLIENT_SECRET\")\n        ```\n\n    3. Put some links to the providers on the new session page (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/templates/auth/session/new.html.eex`)\n\n        ```elixir\n        \u003c%= link \"Google\", to: Routes.ueberauth_path(@conn, :request, \"google\"), class: \"btn btn-default\" %\u003e\n        ```\n\n### Database Authenticatable\n\n#### installation\n\n1. Run the install command\n\n    ```\n    mix curator.database_authenticatable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.DatabaseAuthenticatable,\n      ]\n    ```\n\n3. Update the user schema (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/user.ex`)\n\n    ```elixir\n    # DatabaseAuthenticatable\n    field :password, :string, virtual: true\n    field :password_hash, :string\n    ```\n\n4. Add your crypto_mod dependencies.\n\n  By default, Bcrypt is configured as the crypto_mod. This requires two dependencies:\n\n    ```\n    {:bcrypt_elixir, \"~\u003e 2.0\"},\n    ```\n\n  You can configure the crypto_mod by passing it as an arguement in the DatabaseAuthenticatable implementation.\n\n5. Update the new session page as needed (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/templates/auth/session/new.html.eex`)\n\n6. run the migration\n\n    ```\n    mix ecto.migrate\n    ```\n\n7. If using [Registerable](#registerable), Update the registration form\n\n    ```elixir\n    \u003cdiv class=\"form-group\"\u003e\n      \u003c%= label f, :password, class: \"control-label\" %\u003e\n      \u003c%= password_input f, :password, class: \"form-control\" %\u003e\n      \u003c%= error_tag f, :password %\u003e\n    \u003c/div\u003e\n    ```\n\n8. If you have other use cases, you can use the changeset directly:\n\n  ```elixir\n  \u003cMyApp\u003e.Auth.find_user_by_email(\"test@test.com\")\n  |\u003e \u003cMyAppWeb\u003e.Auth.DatabaseAuthenticatable.update_changeset(%{password: \"test\"})\n  |\u003e \u003cMyApp\u003e.Repo.update()\n  ```\n\n### Registerable\n\n#### Installation\n\n1. Run the install command\n\n    ```\n    mix curator.registerable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.Registerable,\n      ]\n    ```\n\n3. Put a link on the new session page (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/templates/auth/session/new.html.eex`)\n\n    ```elixir\n    \u003c%= link to: Routes.registration_path(@conn, :new), class: \"btn btn-outline-primary\" do %\u003e\n      Register\n    \u003c% end %\u003e\n    ```\n\n4. Add a registration link to your layout\n\n    ```elixir\n    \u003c%= link \"My Account\", to: Routes.registration_path(@conn, :edit) %\u003e\n    ```\n\n### Confirmable\n\n#### installation\n\n1. Run the install command\n\n    ```\n    mix curator.confirmable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.Confirmable,\n      ]\n    ```\n\n3. Update the user schema (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/user.ex`)\n\n    ```elixir\n    # Confirmable\n    field :email_confirmed_at, :utc_datetime\n    ```\n\n4. Testing ... add confirmed_at\n\n### Recoverable\n\n#### installation\n\n1. Run the install command\n\n    ```\n    mix curator.recoverable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.Recoverable,\n      ]\n    ```\n\n3. Put a link on the new session page (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/templates/auth/session/new.html.eex`)\n\n    ```elixir\n    \u003c%= link to: Routes.recoverable_path(@conn, :new), class: \"btn btn-outline-primary\" do %\u003e\n      Forgotten Password\n    \u003c% end %\u003e\n    ```\n\n### Lockable\n\n#### Installation\n\n1. Run the install command\n\n    ```\n    mix curator.lockable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.LockableImpl,\n      ]\n    ```\n\n3. Update the user schema (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/user.ex`)\n\n    ```elixir\n    # Lockable\n    field :failed_attempts, :integer\n    field :locked_at, :utc_datetime\n    ```\n\n### Approvable\n\n#### installation\n\n1. Run the install command\n\n    ```\n    mix curator.approvable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.Approvable,\n      ]\n    ```\n\n3. Update the user schema (`\u003cmy_app\u003e/lib/\u003cmy_app\u003e/auth/user.ex`)\n\n    ```elixir\n    # Approvable\n    field :approval_status, :string, default: \"pending\"\n    field :approval_at, :utc_datetime\n    belongs_to :approver, \u003cMyApp\u003e.Auth.User\n    ```\n\n4. Enable Approvable emails (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/approvable.ex`)\n\n  Emails can be sent after a users registers or confirms their account (or both) by setting the `email_after` configuration to a list of `:registration` and/or `:confirmation`\n\n    ```elixir\n    defmodule \u003cMyAppWeb\u003e.Auth.Approvable do\n      use Curator.Approvable,\n        otp_app: :\u003cmy_app_web\u003e,\n        curator: \u003cMyAppWeb\u003e.Auth.Curator,\n        email_after: [:confirmation] # Add this\n    end\n    ```\n  5. Set the email recipients (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/email.ex`)\n\n    ```elixir\n    defp approvable_emails do\n      # Add your list here\n    end\n    ```\n\n  6. (Todo) The approvers will need a UI.\n\n### Timeoutable\n\n#### Description\nSession Timeout (after configurable inactivity)\n\n#### Installation\n\n1. Run the install command\n\n    ```\n    mix curator.timeoutable.install\n    ```\n\n2. Add to the curator modules (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/curator.ex`)\n\n    ```elixir\n    use Curator,\n      otp_app: :\u003cmy_app_web\u003e,\n      modules: [\n       \u003cMyAppWeb\u003e.Auth.Timeoutable,\n      ]\n    ```\n\n3. Add to the curator plugs\n\n    ```elixir\n    defmodule \u003cMyAppWeb\u003e.Auth.Curator.UnauthenticatedPipeline do\n      ...\n      plug Curator.Timeoutable.Plug, timeoutable_module: \u003cMyAppWeb\u003e.Auth.Timeoutable\n    end\n\n    defmodule \u003cMyAppWeb\u003e.Auth.Curator.AuthenticatedPipeline do\n      ...\n      plug Curator.Timeoutable.Plug, timeoutable_module: \u003cMyAppWeb\u003e.Auth.Timeoutable\n    end\n    ```\n\n4. (optional) Configure Timeoutable (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/auth/timeoutable.ex`)\n\n    ```elixir\n    use Curator.Timeoutable,\n      otp_app: :\u003cmy_app_web\u003e,\n      timeout_in: 1800\n    ```\n\n5. Update tests (`\u003cmy_app_web\u003e/test/support/conn_case.ex`)\n\n    ```elixir\n    auth_conn = conn\n    |\u003e Plug.Test.init_test_session(%{\n      guardian_default_token: token,\n      guardian_default_timeoutable: Curator.Time.timestamp(),\n    })\n    ```\n\n    This session key usually is set as part of the after_sign_in extension.\n\n6. (optional) Update the ErrorHandler (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/controllers/auth/error_handler.ex`)\n\n    ```elixir\n    defp translate_error({:timeoutable, :timeout}), do: \"You have been signed out due to inactivity\"\n    ```\n\n### API\n\n#### Description\nAPI Login (with an opaque token)\n\nThis generator uses the `Curator.Guardian.Token.Opaque` module in place of the guardian default `Guardian.Token.Jwt`. It also assumes you'll be storing them in an ecto database. Various backends could be used, as long as they implement the Curator.Guardian.Token.Opaque.Persistence behaviour. If you prefer JWT throughout, you can remove the schema / context and set the guardian_token to the default (TODO: accept a command line option to do this).\n\n#### Installation\n\n1. Run the install command\n\n    ```\n    mix curator.api.install\n    ```\n\n2. Update your router (`\u003cmy_app_web\u003e/lib/\u003cmy_app_web\u003e/router.ex`)\n\n    ```elixir\n    require Curator.Router\n\n    pipeline :api do\n      plug :accepts, [\"json\"]\n\n      plug \u003cMyAppWeb\u003e.Auth.Curator.ApiPipeline\n    end\n\n    scope \"/api\", \u003cMyAppWeb\u003e do\n      pipe_through :api\n\n      ...\n    end\n    ```\n\n3. Testing\n\n    Update `conn_case.ex`:\n\n    ```elixir\n    setup tags do\n      ...\n\n      api_unauth_conn = Phoenix.ConnTest.build_conn() |\u003e Plug.Conn.put_req_header(\"accept\", \"application/json\")\n\n      {:ok, token_id, _claims} = \u003cMyAppWeb\u003e.Auth.OpaqueGuardian.encode_and_sign(auth_user, %{description: \"TEST\"}, token_type: \"api\")\n      api_auth_conn = Plug.Conn.put_req_header(api_unauth_conn, \"authorization\", \"Bearer: #{token_id}\")\n\n      api_invalid_conn = Plug.Conn.put_req_header(api_unauth_conn, \"authorization\", \"Bearer: NOT_A_REAL_TOKEN\")\n\n      {:ok,\n        ...\n        api_unauth_conn: api_unauth_conn,\n        api_auth_conn: api_auth_conn,\n        api_invalid_conn: api_invalid_conn,\n      }\n    end\n    ```\n\n    To test, I created some special routes:\n\n    ```elixir\n    scope \"/api\", \u003cMyAppWeb\u003e do\n      pipe_through :api\n\n      if Mix.env == :test do\n        get \"/secure\", PageController, :json_secure\n      end\n    ```\n\n    Update the `page_controller.ex`:\n\n    ```elixir\n    defmodule \u003cMyAppWeb\u003e.PageController do\n      use \u003cMyAppWeb\u003e, :controller\n\n      def json_secure(conn, _params) do\n        json conn, %{data: \"SECURE\"}\n      end\n    end\n    ```\n\n    And wrote tests in `page_controller_test.exs`:\n\n    ```\n    defmodule \u003cMyAppWeb\u003e.PageControllerTest do\n      use \u003cMyAppWeb\u003e.ConnCase\n\n      describe \"API\" do\n        test \"GET /secure (Unauthenticated)\", %{api_unauth_conn: conn} do\n          conn = get conn, \"/api/secure\"\n          assert json_response(conn, 403) == %{\"error\" =\u003e \"No API Token\"}\n        end\n\n        test \"GET /secure (Authenticated)\", %{api_auth_conn: conn} do\n          conn = get conn, \"/api/secure\"\n          assert json_response(conn, 200) == %{\"data\" =\u003e \"SECURE\"}\n        end\n\n        test \"GET /secure (Bad Token)\", %{api_invalid_conn: conn} do\n          conn = get conn, \"/api/secure\"\n          assert json_response(conn, 403) == %{\"error\" =\u003e \"Invalid API Token\"}\n        end\n      end\n    end\n    ```\n\n# Extending Curator (TODO)\n\n# Debt\nThanks go out to the [Phoenix Team](https://github.com/phoenixframework/phoenix), the original rails gem [Devise](https://github.com/plataformatec/devise), [Guardian](https://github.com/ueberauth/guardian), and the other elixir authentication solutions:\n\n* [Coherence](https://github.com/smpallen99/coherence)\n* [Sentinel](https://github.com/britton-jb/sentinel)\n* [Openmaize](https://github.com/riverrun/openmaize)\n\nAny decent ideas I credit to them, I was just acting as the curator.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurator-ex%2Fcurator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcurator-ex%2Fcurator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcurator-ex%2Fcurator/lists"}