mirror of
https://github.com/team-alembic/ash_authentication_phoenix.git
synced 2024-09-20 13:34:26 +12:00
docs: Rewrite of the getting started tutorial (#90)
* Update the Getting Started tutorial. * Bump ash_authentication_phoenix version * Fix duplication
This commit is contained in:
parent
a6a10f8d93
commit
3c9b69f235
1 changed files with 445 additions and 147 deletions
|
@ -1,13 +1,17 @@
|
||||||
# Getting Started Ash Authentication Phoenix
|
# Getting Started Ash Authentication Phoenix
|
||||||
|
|
||||||
This guide assumes that you already have an Phoenix application set up with Ash.
|
In this 10 minute tutorial we create a new empty `Example` Phoenix application which provides the functionality to register users with their email address and a password. Registered users can sign in and out.
|
||||||
If you don't then check out the [Phoenix topic on Ash
|
|
||||||
HQ](https://ash-hq.org/docs/guides/ash/latest/topics/phoenix).
|
|
||||||
|
|
||||||
|
We assumes that you have [Elixir](https://elixir-lang.org) version 1.14.x (check with `elixir -v`) and Phoenix 1.7 (check with `mix phx.new --version`) installed. We also assume that you have a [PostgreSQL](https://www.postgresql.org) database running which we use to persist the user data.
|
||||||
|
|
||||||
## Before continuing
|
## Green Field
|
||||||
|
|
||||||
If you haven't already, read and follow [Getting Started with Ash Authentication](https://ash-hq.org/docs/guides/ash_authentication/latest/tutorials/getting-started-with-authentication). You will use the things you create there in this tutorial.
|
We start with a new Phoenix application and use the `--no-ecto` flag to skip the [Ecto](https://hexdocs.pm/ecto/Ecto.html) setup.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix phx.new example --no-ecto
|
||||||
|
$ cd example
|
||||||
|
```
|
||||||
|
|
||||||
## Add to your application's dependencies
|
## Add to your application's dependencies
|
||||||
|
|
||||||
|
@ -22,66 +26,352 @@ defp deps()
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
Add `ash_authentication_phoenix` to your `.formatter.exs`:
|
## Application Dependencies
|
||||||
|
|
||||||
|
We need to add the following dependencies:
|
||||||
|
|
||||||
|
** `mix.exs` **
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# .formatter.exs
|
defmodule Example.MixProject do
|
||||||
|
use Mix.Project
|
||||||
[
|
|
||||||
# ...
|
# ...
|
||||||
import_deps: [:ash_authentication_phoenix]
|
|
||||||
|
defp deps do
|
||||||
|
[
|
||||||
|
# ...
|
||||||
|
# add these lines -->
|
||||||
|
{:ash, "~> 2.5.11"},
|
||||||
|
{:ash_authentication, "~> 3.7.3"},
|
||||||
|
{:ash_authentication_phoenix, "~> 1.4.6"},
|
||||||
|
{:ash_postgres, "~> 1.3.2"},
|
||||||
|
{:elixir_sense, github: "elixir-lsp/elixir_sense", only: [:dev, :test]}
|
||||||
|
# <-- add these lines
|
||||||
|
]
|
||||||
|
end
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
> The `elixir_sense` line is optional but recommended for Visual Studio Code users. It provides code completion functionality.
|
||||||
|
|
||||||
|
Let's fetch everything:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix deps.get
|
||||||
|
```
|
||||||
|
|
||||||
|
## Formatter
|
||||||
|
|
||||||
|
We can make our life easier and the code more consistent by adding formatters to the project. We will use [Elixir's built-in formatter](https://hexdocs.pm/mix/master/Mix.Tasks.Format.html) for this.
|
||||||
|
|
||||||
|
** `.formatter.exs` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
[
|
||||||
|
import_deps: [
|
||||||
|
:phoenix,
|
||||||
|
# add these lines -->
|
||||||
|
:ash,
|
||||||
|
:ash_authentication_phoenix,
|
||||||
|
:ash_postgres
|
||||||
|
# <-- add these lines
|
||||||
|
],
|
||||||
|
plugins: [Phoenix.LiveView.HTMLFormatter],
|
||||||
|
inputs: ["*.{heex,ex,exs}", "{config,lib,test}/**/*.{heex,ex,exs}"]
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
## Phoenix 1.7 compatibility
|
## Phoenix 1.7 compatibility
|
||||||
|
|
||||||
In `your_app_web.ex` you will need to change `helpers: false` to `helpers: true` in the router section. AshAuthenticationPhoenix relies on
|
For Phoenix 1.7 we need to change `helpers: false` to `helpers: true` in the router section:
|
||||||
these helpers to know where your authenticated routes are.
|
|
||||||
|
|
||||||
## `AshAuthentication.Phoenix.Router`
|
** `lib/example_web.ex` **
|
||||||
|
|
||||||
`ash_authentication_phoenix` includes several helper macros which can generate
|
|
||||||
Phoenix routes for you. They are included by way of a `use` macro:
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# lib/my_app_web/router.ex
|
defmodule ExampleWeb do
|
||||||
|
# ...
|
||||||
defmodule MyAppWeb.Router do
|
def router do
|
||||||
use MyAppWeb, :router
|
quote do
|
||||||
use AshAuthentication.Phoenix.Router
|
use Phoenix.Router, helpers: true # <-- Change this line
|
||||||
|
|
||||||
pipeline :browser do
|
|
||||||
# ...
|
# ...
|
||||||
plug(:load_from_session)
|
```
|
||||||
end
|
|
||||||
|
|
||||||
pipeline :api do
|
## Create and configure the Repo
|
||||||
# ...
|
|
||||||
plug(:load_from_bearer)
|
|
||||||
end
|
|
||||||
|
|
||||||
scope "/", MyAppWeb do
|
We use [AshPostgres](https://hexdocs.pm/ash_postgres/AshPostgres.html) to handle the database tables for our application. We need to create a new `Repo` module for that:
|
||||||
pipe_through :browser
|
|
||||||
sign_in_route
|
** `lib/example/repo.ex` **
|
||||||
sign_out_route AuthController
|
|
||||||
auth_routes_for MyApp.Accounts.User, to: AuthController
|
```elixir
|
||||||
|
defmodule Example.Repo do
|
||||||
|
use AshPostgres.Repo, otp_app: :example
|
||||||
|
|
||||||
|
def installed_extensions do
|
||||||
|
["uuid-ossp", "citext"]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### `AshAuthentication.Phoenix.Router.sign_in_route/3`
|
We have to configure the Repo in `config/config.exs`. While doing that we also configure other stuff which we need later.
|
||||||
|
|
||||||
This helper generates a live route to the `AshAuthentication.Phoenix.SignInLive`
|
** `config/config.exs` **
|
||||||
LiveView. This LiveView renders a generic sign-in/register screen. It is
|
|
||||||
entirely optional, and can be customised either by way of `AshAuthentication.Phoenix.Overrides` or replaced entirely.
|
|
||||||
|
|
||||||
### `AshAuthentication.Phoenix.Router.sign_out_route/3`
|
```elixir
|
||||||
|
# ...
|
||||||
|
|
||||||
This helper generates a route which points to the `sign_out` action in your `AuthController`.
|
import Config
|
||||||
|
|
||||||
### `AshAuthentication.Phoenix.Router.auth_routes_for/2`
|
# add these lines -->
|
||||||
|
config :example,
|
||||||
|
ash_apis: [Example.Accounts]
|
||||||
|
|
||||||
This helper generates all the required routes for all strategies supported by the provided resource.
|
config :example,
|
||||||
|
ecto_repos: [Example.Repo]
|
||||||
|
|
||||||
|
config :ash,
|
||||||
|
:use_all_identities_in_manage_relationship?, false
|
||||||
|
# <-- add these lines
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
We need to add the `Repo` to the supervision tree in `lib/example/application.ex`:
|
||||||
|
|
||||||
|
`** lib/example/application.ex **`
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Example.Application do
|
||||||
|
# ...
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def start(_type, _args) do
|
||||||
|
children = [
|
||||||
|
# ...
|
||||||
|
# add these lines -->
|
||||||
|
Example.Repo,
|
||||||
|
{AshAuthentication.Supervisor, otp_app: :example}
|
||||||
|
# <-- add these lines
|
||||||
|
]
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
### Database Configuration
|
||||||
|
|
||||||
|
In case you have other `usernames` and `passwords` for your database you need to change the following values. We use the default `postgres` user and password.
|
||||||
|
|
||||||
|
** `config/dev.exs` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
import Config
|
||||||
|
|
||||||
|
# add these lines -->
|
||||||
|
config :example, Example.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
hostname: "localhost",
|
||||||
|
database: "example_dev",
|
||||||
|
port: 5432,
|
||||||
|
show_sensitive_data_on_connection_error: true,
|
||||||
|
pool_size: 10
|
||||||
|
# <-- add these lines
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
** `config/test.exs` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
import Config
|
||||||
|
|
||||||
|
# add these lines -->
|
||||||
|
config :example, Example.Repo,
|
||||||
|
username: "postgres",
|
||||||
|
password: "postgres",
|
||||||
|
hostname: "localhost",
|
||||||
|
database: "example_test#{System.get_env("MIX_TEST_PARTITION")}",
|
||||||
|
pool: Ecto.Adapters.SQL.Sandbox,
|
||||||
|
pool_size: 10
|
||||||
|
# <-- add these lines
|
||||||
|
```
|
||||||
|
|
||||||
|
** `config/runtime.exs` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
import Config
|
||||||
|
|
||||||
|
# ...
|
||||||
|
|
||||||
|
if config_env() == :prod do
|
||||||
|
# add these lines -->
|
||||||
|
database_url =
|
||||||
|
System.get_env("DATABASE_URL") ||
|
||||||
|
raise """
|
||||||
|
environment variable DATABASE_URL is missing.
|
||||||
|
For example: ecto://USER:PASS@HOST/DATABASE
|
||||||
|
"""
|
||||||
|
|
||||||
|
config :example, Example.Repo,
|
||||||
|
url: database_url,
|
||||||
|
pool_size: String.to_integer(System.get_env("POOL_SIZE") || "10")
|
||||||
|
# <-- add these lines
|
||||||
|
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
## Create an Accounts API
|
||||||
|
|
||||||
|
We need to create an `Accounts` API in our application to provide a `User` and a `Token` resource. For that we create a couple of new files and one new directory. At the end we should have the following directory structure:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
lib/example
|
||||||
|
├── accounts
|
||||||
|
│ ├── registry.ex
|
||||||
|
│ ├── token.ex
|
||||||
|
│ └── user.ex
|
||||||
|
├── accounts.ex
|
||||||
|
...
|
||||||
|
```
|
||||||
|
|
||||||
|
** `lib/example/accounts.ex` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Example.Accounts do
|
||||||
|
use Ash.Api
|
||||||
|
|
||||||
|
resources do
|
||||||
|
registry Example.Accounts.Registry
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
** `lib/example/accounts/user.ex` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Example.Accounts.User do
|
||||||
|
use Ash.Resource,
|
||||||
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
extensions: [AshAuthentication]
|
||||||
|
|
||||||
|
attributes do
|
||||||
|
uuid_primary_key :id
|
||||||
|
attribute :email, :ci_string, allow_nil?: false
|
||||||
|
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
|
||||||
|
end
|
||||||
|
|
||||||
|
authentication do
|
||||||
|
api Example.Accounts
|
||||||
|
|
||||||
|
strategies do
|
||||||
|
password :password do
|
||||||
|
identity_field(:email)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
tokens do
|
||||||
|
enabled?(true)
|
||||||
|
token_resource(Example.Accounts.Token)
|
||||||
|
|
||||||
|
signing_secret(Application.get_env(:example, ExampleWeb.Endpoint)[:secret_key_base])
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
postgres do
|
||||||
|
table "users"
|
||||||
|
repo Example.Repo
|
||||||
|
end
|
||||||
|
|
||||||
|
identities do
|
||||||
|
identity :unique_email, [:email]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
** `lib/example/token.ex` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Example.Accounts.Token do
|
||||||
|
use Ash.Resource,
|
||||||
|
data_layer: AshPostgres.DataLayer,
|
||||||
|
extensions: [AshAuthentication.TokenResource]
|
||||||
|
|
||||||
|
token do
|
||||||
|
api Example.Accounts
|
||||||
|
end
|
||||||
|
|
||||||
|
postgres do
|
||||||
|
table "tokens"
|
||||||
|
repo Example.Repo
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
Next, let's define our registry:
|
||||||
|
|
||||||
|
** `lib/example/accounts/registry.ex` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule Example.Accounts.Registry do
|
||||||
|
use Ash.Registry, extensions: [Ash.Registry.ResourceValidations]
|
||||||
|
|
||||||
|
entries do
|
||||||
|
entry Example.Accounts.User
|
||||||
|
entry Example.Accounts.Token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
|
### Migration and Create
|
||||||
|
|
||||||
|
Now is a good time to create the database and run the migrations:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix ash_postgres.create
|
||||||
|
$ mix ash_postgres.generate_migrations --name add_user_and_token
|
||||||
|
$ mix ash_postgres.migrate
|
||||||
|
```
|
||||||
|
|
||||||
|
> In case you want to drop the database and start over again during development you can use `mix ash_postgres.drop` followed by `mix ash_postgres.create` and `mix ash_postgres.migrate`.
|
||||||
|
|
||||||
|
## `AshAuthentication.Phoenix.Router`
|
||||||
|
|
||||||
|
`ash_authentication_phoenix` includes several helper macros which can generate
|
||||||
|
Phoenix routes for you. For that you need to add 6 lines in the router module:
|
||||||
|
|
||||||
|
** `lib/example_web/router.ex` **
|
||||||
|
|
||||||
|
```elixir
|
||||||
|
defmodule ExampleWeb.Router do
|
||||||
|
use ExampleWeb, :router
|
||||||
|
use AshAuthentication.Phoenix.Router # <--- Add this line
|
||||||
|
|
||||||
|
pipeline :browser do
|
||||||
|
plug :accepts, ["html"]
|
||||||
|
plug :fetch_session
|
||||||
|
plug :fetch_live_flash
|
||||||
|
plug :put_root_layout, {ExampleWeb.Layouts, :root}
|
||||||
|
plug :protect_from_forgery
|
||||||
|
plug :put_secure_browser_headers
|
||||||
|
plug :load_from_session # <--- Add this line
|
||||||
|
end
|
||||||
|
|
||||||
|
pipeline :api do
|
||||||
|
plug :accepts, ["json"]
|
||||||
|
plug :load_from_bearer # <--- Add this line
|
||||||
|
end
|
||||||
|
|
||||||
|
scope "/", ExampleWeb do
|
||||||
|
pipe_through :browser
|
||||||
|
|
||||||
|
# add these lines -->
|
||||||
|
sign_in_route()
|
||||||
|
sign_out_route AuthController
|
||||||
|
auth_routes_for Example.Accounts.User, to: AuthController
|
||||||
|
# <-- add these lines
|
||||||
|
|
||||||
|
get "/", PageController, :home
|
||||||
|
end
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
### Generated routes
|
### Generated routes
|
||||||
|
|
||||||
|
@ -90,40 +380,28 @@ Given the above configuration you should see the following in your routes:
|
||||||
```
|
```
|
||||||
# mix phx.routes
|
# mix phx.routes
|
||||||
|
|
||||||
auth_path * /auth/user/confirm MyAppWeb.AuthController {:user, :confirm, :confirm}
|
Generated example app
|
||||||
auth_path * /auth/user/password/register MyAppWeb.AuthController {:user, :password, :register}
|
auth_path GET /sign-in AshAuthentication.Phoenix.SignInLive :sign_in
|
||||||
auth_path * /auth/user/password/sign_in MyAppWeb.AuthController {:user, :password, :sign_in}
|
auth_path GET /sign-out ExampleWeb.AuthController :sign_out
|
||||||
auth_path * /auth/user/password/reset_request MyAppWeb.AuthController {:user, :password, :reset_request}
|
auth_path * /auth/user/password/register ExampleWeb.AuthController {:user, :password, :register}
|
||||||
auth_path * /auth/user/password/reset MyAppWeb.AuthController {:user, :password, :reset}
|
auth_path * /auth/user/password/sign_in ExampleWeb.AuthController {:user, :password, :sign_in}
|
||||||
auth_path GET /sign-in AshAuthentication.Phoenix.SignInLive :sign_in
|
page_path GET / ExampleWeb.PageController :home
|
||||||
auth_path GET /sign-out MyAppWeb.AuthController :sign_out
|
...
|
||||||
```
|
```
|
||||||
|
|
||||||
## `AshAuthentication.Phoenix.Controller`
|
## `AshAuthentication.Phoenix.Controller`
|
||||||
|
|
||||||
Instead of using `AshAuthentication.Plug` as
|
While running `mix phx.routes` you probably saw the warning message that the `ExampleWeb.AuthController.init/1 is undefined`. Let's fix that by creating a new controller:
|
||||||
suggested in [the previous guide](/documentation/tutorials/getting-started-with-authentication.md),
|
|
||||||
`ash_authentication_phoenix` comes with a generator which creates a
|
|
||||||
`Phoenix.Controller` by way of a `use` macro.
|
|
||||||
|
|
||||||
All functions in `AshAuthentication.Phoenix.Plug` are automatically imported.
|
** `lib/my_app_web/controllers/auth_controller.ex` **
|
||||||
|
|
||||||
You can define multiple versions if required (eg one for your `:api` pipeline
|
|
||||||
and another for your `:browser` pipeline). Let's define a version for a browser
|
|
||||||
client:
|
|
||||||
|
|
||||||
> Remember to define an appropriate template in `failure.html.heex` for your
|
|
||||||
> controller. Alternatively, you could redirect with a flash message.
|
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
# lib/my_app_web/controllers/auth_controller.ex
|
defmodule ExampleWeb.AuthController do
|
||||||
|
use ExampleWeb, :controller
|
||||||
defmodule MyAppWeb.Controllers.AuthController do
|
|
||||||
use MyAppWeb, :controller
|
|
||||||
use AshAuthentication.Phoenix.Controller
|
use AshAuthentication.Phoenix.Controller
|
||||||
|
|
||||||
def success(conn, _activity, user, _token) do
|
def success(conn, _activity, user, _token) do
|
||||||
return_to = get_session(conn, :return_to) || Routes.path_path(conn, :index)
|
return_to = get_session(conn, :return_to) || ~p"/"
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> delete_session(:return_to)
|
|> delete_session(:return_to)
|
||||||
|
@ -139,7 +417,7 @@ defmodule MyAppWeb.Controllers.AuthController do
|
||||||
end
|
end
|
||||||
|
|
||||||
def sign_out(conn, _params) do
|
def sign_out(conn, _params) do
|
||||||
return_to = get_session(conn, :return_to) || Routes.path_path(conn, :index)
|
return_to = get_session(conn, :return_to) || ~p"/"
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> clear_session()
|
|> clear_session()
|
||||||
|
@ -148,83 +426,21 @@ defmodule MyAppWeb.Controllers.AuthController do
|
||||||
end
|
end
|
||||||
```
|
```
|
||||||
|
|
||||||
### `success/4`
|
** `lib/example_web/controller/auth_html.ex` **
|
||||||
|
|
||||||
This callback is called when registration or sign-in is successful. You should
|
```elixir
|
||||||
use it to prepare a response back to the user indicating that authentication was
|
defmodule ExampleWeb.AuthHTML do
|
||||||
successful.
|
use ExampleWeb, :html
|
||||||
|
|
||||||
It is called with the following arguments:
|
embed_templates "auth_html/*"
|
||||||
|
end
|
||||||
|
```
|
||||||
|
|
||||||
* `conn` the Plug connection.
|
** `lib/example_web/controllers/auth_html/failure.html.heex` **
|
||||||
* `activity` a tuple containing two atoms - the strategy name and the phase.
|
|
||||||
You can use this if you need to provide different behaviour depending on the
|
|
||||||
authentication method.
|
|
||||||
* `user` the authenticated user record (ie an instance of your user resource).
|
|
||||||
* `token` a string containing a JWT for this user, if tokens are enabled.
|
|
||||||
Otherwise `nil`.
|
|
||||||
|
|
||||||
In the example above we set up the session to know who the user is on their next
|
```html
|
||||||
request and redirect them to an appropriate location.
|
<h1 class="text-2xl">Authentication Error</h1>
|
||||||
|
```
|
||||||
### `failure/3`
|
|
||||||
|
|
||||||
This callback is called when registration or sign-in is unsuccessful. You
|
|
||||||
should use this to render an error, or provide some other indication to the user
|
|
||||||
that authentication has failed.
|
|
||||||
|
|
||||||
It is called with the following arguments:
|
|
||||||
|
|
||||||
* `conn` the Plug connection.
|
|
||||||
* `activity` a tuple containing two atoms - the strategy name and the phase.
|
|
||||||
You can use this if you need to provide different behaviour depending on the
|
|
||||||
authentication method.
|
|
||||||
* The reason for failure. It _could_ be an `Ash.Error`, an `Ash.Changeset`,
|
|
||||||
or any other failure.
|
|
||||||
|
|
||||||
In the example above we simply set the HTTP status to 401 and render an HTML page.
|
|
||||||
|
|
||||||
### `sign_out/2`
|
|
||||||
|
|
||||||
This is not strictly necessary, but if you have enabled the
|
|
||||||
`AshAuthentication.Phoenix.Router.sign_out_route/3`
|
|
||||||
helper in your router, then this is the controller action which will be called.
|
|
||||||
Use this to perform any sign-out actions (like clearing the session or `AshAuthentication.Phoenix.Plug.revoke_bearer_tokens/2` and then sending the user on their way.
|
|
||||||
|
|
||||||
## Component library
|
|
||||||
|
|
||||||
`ash_authentication_phoenix` ships with a number of components allowing you to
|
|
||||||
pick the level of customisation you require.
|
|
||||||
|
|
||||||
* `AshAuthentication.Phoenix.Components.SignIn`
|
|
||||||
This is the top-level component, given a [resource
|
|
||||||
configuration](t:AshAuthentication.resource_config) it will iterate through
|
|
||||||
all the configured authentication providers and render their UI. You can
|
|
||||||
place this directly into your sign-in page if you want.
|
|
||||||
* `AshAuthentication.Phoenix.Components.Password`
|
|
||||||
This component renders the UI for password authentication - both the
|
|
||||||
registration and sign-in UI.
|
|
||||||
* `AshAuthentication.Phoenix.Components.Password.SignInForm`
|
|
||||||
This component renders the UI for a password authentication sign-in form.
|
|
||||||
* `AshAuthentication.Phoenix.Components.Password.RegisterForm`
|
|
||||||
This component renders the UI for a password authentication registration
|
|
||||||
form.
|
|
||||||
* `AshAuthentication.Phoenix.Components.Password.ResetForm`
|
|
||||||
This component renders the UI for a user to request a password reset.
|
|
||||||
* `AshAuthentication.Phoenix.Components.Password.Input`
|
|
||||||
This module contains several function components which provide individual
|
|
||||||
input fields and buttons for password authentication.
|
|
||||||
* `AshAuthentication.Phoenix.Components.OAuth2`
|
|
||||||
A component which renders a sign-in button for an OAuth 2.0 provider.
|
|
||||||
|
|
||||||
### Overrides
|
|
||||||
|
|
||||||
All the components above and the `AshAuthentication.Phoenix.SignInLive`
|
|
||||||
LiveView are customisable via the `AshAuthentication.Phoenix.Overrides`
|
|
||||||
system.
|
|
||||||
|
|
||||||
Overrides allow you to configure CSS classes and other options for the
|
|
||||||
components without needing to modify them.
|
|
||||||
|
|
||||||
### Tailwind
|
### Tailwind
|
||||||
|
|
||||||
|
@ -233,16 +449,98 @@ components without overriding them you will need to modify your
|
||||||
`assets/tailwind.config.js` to include the `ash_authentication_phoenix`
|
`assets/tailwind.config.js` to include the `ash_authentication_phoenix`
|
||||||
dependency:
|
dependency:
|
||||||
|
|
||||||
|
** `assets/tailwind.config.js` **
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
|
// See the Tailwind configuration guide for advanced usage
|
||||||
|
// https://tailwindcss.com/docs/configuration
|
||||||
|
|
||||||
|
const plugin = require("tailwindcss/plugin")
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
content: [
|
content: [
|
||||||
// Other paths.
|
"./js/**/*.js",
|
||||||
"../deps/ash_authentication_phoenix/**/*.ex"
|
"../lib/*_web.ex",
|
||||||
]
|
"../lib/*_web/**/*.*ex",
|
||||||
}
|
"../deps/ash_authentication_phoenix/**/*.ex" // <-- Add this line
|
||||||
|
],
|
||||||
|
// ...
|
||||||
```
|
```
|
||||||
|
|
||||||
## Summary
|
## Minimal Example
|
||||||
|
|
||||||
In this guide we've learned how to add Ash Authentication to Phoenix, configure
|
To see how the authentication works we replace the default Phoenix `home.html.eex` with a minimal example which has a top navbar. On the right side it shows the `@current_user` and a sign out button. If you are not signed in you will see a sign in button.
|
||||||
routes and handle authentication.
|
|
||||||
|
** `lib/example_web/controllers/page_html/home.html.heex` **
|
||||||
|
|
||||||
|
```html
|
||||||
|
<nav class="bg-gray-800">
|
||||||
|
<div class="px-2 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
|
<div class="relative flex items-center justify-between h-16">
|
||||||
|
<div class="flex items-center justify-center flex-1 sm:items-stretch sm:justify-start">
|
||||||
|
<div class="block ml-6">
|
||||||
|
<div class="flex space-x-4">
|
||||||
|
<div class="px-3 py-2 text-xl font-medium text-white ">
|
||||||
|
Ash Demo
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="absolute inset-y-0 right-0 flex items-center pr-2 sm:static sm:inset-auto sm:ml-6 sm:pr-0">
|
||||||
|
<%= if @current_user do %>
|
||||||
|
<span class="px-3 py-2 text-sm font-medium text-white rounded-md">
|
||||||
|
<%= @current_user.email %>
|
||||||
|
</span>
|
||||||
|
<a
|
||||||
|
href="/sign-out"
|
||||||
|
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
|
||||||
|
>
|
||||||
|
Sign out
|
||||||
|
</a>
|
||||||
|
<% else %>
|
||||||
|
<a
|
||||||
|
href="/sign-in"
|
||||||
|
class="rounded-lg bg-zinc-100 px-2 py-1 text-[0.8125rem] font-semibold leading-6 text-zinc-900 hover:bg-zinc-200/80 active:text-zinc-900/70"
|
||||||
|
>
|
||||||
|
Sign In
|
||||||
|
</a>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<div class="py-10">
|
||||||
|
<header>
|
||||||
|
<div class="px-4 mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
|
<h1 class="text-3xl font-bold leading-tight tracking-tight text-gray-900">Demo</h1>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<main>
|
||||||
|
<div class="mx-auto max-w-7xl sm:px-6 lg:px-8">
|
||||||
|
<div class="px-4 py-8 sm:px-0">
|
||||||
|
<div class="border-4 border-gray-200 border-dashed rounded-lg h-96"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Start Phoenix
|
||||||
|
|
||||||
|
You can now start Phoenix and visit
|
||||||
|
[`localhost:4000`](http://localhost:4000) from your browser.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ mix phx.server
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sign In
|
||||||
|
|
||||||
|
Visit [`localhost:4000/sign-in`](http://localhost:4000/sign-in) from your browser.
|
||||||
|
|
||||||
|
The sign in page shows a link to register a new account.
|
||||||
|
|
||||||
|
### Sign Out
|
||||||
|
|
||||||
|
Visit [`localhost:4000/sign-out`](http://localhost:4000/sign-out) from your browser.
|
||||||
|
|
Loading…
Reference in a new issue