2024-05-08 11:50:57 +12:00
# Get started with Ash Authentication
2022-12-08 15:23:47 +13:00
If you haven't already, read [the getting started guide for
Ash](https://ash-hq.org/docs/guides/ash/latest/tutorials/get-started.md). This
assumes that you already have resources set up, and only gives you the steps to
add authentication to your resources and APIs.
## Add to your application's dependencies
Bring in the `ash_authentication` dependency:
```elixir
# mix.exs
defp deps()
[
# ...
2024-05-13 10:25:52 +12:00
{:ash_authentication, "~> 4.0"}
2022-12-08 15:23:47 +13:00
]
end
```
2023-01-17 17:05:19 +13:00
And add `ash_authentication` to your `.formatter.exs` :
```elixir
# .formatter.exs
[
2023-10-16 00:33:44 +13:00
import_deps: [..., :ash_authentication]
2023-01-17 17:05:19 +13:00
]
```
2022-12-08 15:23:47 +13:00
## Choosing your extensions, strategies and add-ons
2022-12-08 18:39:22 +13:00
Ash Authentication supports many different features, each configured separately.
2022-12-08 15:23:47 +13:00
### `AshAuthentication`
This is the core extension, and is required. It provides main DSL for working
with authentication and related features and should be added to your "user"
resource.
2023-01-18 20:13:30 +13:00
The `AshAuthentication` extension provides configuration and sensible defaults for settings which relate to authentication, regardless of authentication mechanism.
2022-12-08 15:23:47 +13:00
All strategy and add-on configuration is nested inside this DSL block.
It will define a `get_by_subject_name` read action on your resource, which is
used when converting tokens or session information into a resource record.
### `AshAuthentication.Strategy.Password`
This authentication strategy provides registration and sign-in for users using a local
2024-03-28 14:39:53 +13:00
identifier (eg `username` , `email` or `phone_number` ) and a password. It will
define register and sign-in actions on your "user" resource. You are welcome to
2022-12-08 15:23:47 +13:00
define either or both of these actions yourself if you wish to customise them -
2024-04-08 00:03:01 +12:00
if you do so then the extension will do its best to validate that all required
2022-12-08 15:23:47 +13:00
configuration is present.
2023-01-18 20:13:30 +13:00
The `AshAuthentication.Strategy.Password` DSL allows you to override any of the default values.
2022-12-08 15:23:47 +13:00
### `AshAuthentication.Strategy.OAuth2`
2022-12-08 18:39:22 +13:00
This authentication strategy provides registration and sign-in for users using a
2024-03-28 14:39:53 +13:00
remote [OAuth 2.0 ](https://oauth.net/2/ ) server as the source of truth. You
2022-12-08 15:23:47 +13:00
will be required to provide either a "register" or a "sign-in" action depending
on your configuration, which the strategy will attempt to validate for common
misconfigurations.
### `AshAuthentication.AddOn.Confirmation`
This add-on allows you to confirm changes to a user record by generating and
sending them a confirmation token which they must submit before allowing the
change to take place.
### `AshAuthentication.TokenResource`
This extension allows you to easily create a resource which will store
2024-03-28 14:39:53 +13:00
information about tokens that can't be encoded into the tokens themselves. A
2022-12-08 15:23:47 +13:00
resource with this extension must be present if token generation is enabled.
### `AshAuthentication.UserIdentity`
If you plan to support multiple different strategies at once (eg giving your
users the choice of more than one authentication provider, or signing them into
multiple services simultaneously) then you will want to create a resource with
2024-03-28 14:39:53 +13:00
this extension enabled. It is used to keep track of the links between your
2022-12-08 15:23:47 +13:00
local user records and their many remote identities.
## Example
2024-03-28 14:39:53 +13:00
Let's create an `Accounts` domain in our application which provides a `User`
2022-12-08 15:23:47 +13:00
resource and a `Token` resource.
2024-03-28 14:39:53 +13:00
First, let's define our domain:
2022-12-08 15:23:47 +13:00
```elixir
# lib/my_app/accounts.ex
defmodule MyApp.Accounts do
2024-03-28 14:39:53 +13:00
use Ash.Domain
2022-12-08 15:23:47 +13:00
resources do
2023-08-16 19:58:48 +12:00
resource MyApp.Accounts.User
resource MyApp.Accounts.Token
2022-12-08 15:23:47 +13:00
end
end
```
2024-03-28 14:39:53 +13:00
Be sure to add it to the `ash_domains` config in your `config.exs`
2023-01-26 11:51:03 +13:00
```elixir
# in config/config.exs
2024-05-13 02:09:45 +12:00
config :my_app, ash_domains: [..., MyApp.Accounts]
2023-01-26 11:51:03 +13:00
```
2024-03-28 14:39:53 +13:00
Next, let's define our `Token` resource. This resource is needed
if token generation is enabled for any resources in your application. Most of
2022-12-08 15:23:47 +13:00
the contents are auto-generated, so we just need to provide the data layer
2022-12-09 11:32:34 +13:00
configuration and the API to use.
2023-10-06 10:08:21 +13:00
But before we do, we need to install a postgres extension.
```elixir
# lib/my_app/repo.ex
defmodule MyApp.Repo do
use AshPostgres.Repo, otp_app: :my_app
2024-03-28 14:39:53 +13:00
2023-10-06 10:08:21 +13:00
def installed_extensions do
2024-05-13 10:25:52 +12:00
["ash-functions", "uuid-ossp", "citext"]
2023-10-06 10:08:21 +13:00
end
end
```
2022-12-09 11:32:34 +13:00
You can skip this step if you don't want to use tokens, in which case remove the
`tokens` DSL section in the user resource below.
2022-12-08 15:23:47 +13:00
```elixir
2024-04-08 00:03:01 +12:00
# lib/my_app/accounts/token.ex
2022-12-08 15:23:47 +13:00
defmodule MyApp.Accounts.Token do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
2024-03-28 14:39:53 +13:00
extensions: [AshAuthentication.TokenResource],
2024-05-14 04:38:07 +12:00
# If using policies, enable the policy authorizer:
# authorizers: [Ash.Policy.Authorizer],
2024-03-28 14:39:53 +13:00
domain: MyApp.Accounts
2022-12-08 15:23:47 +13:00
postgres do
table "tokens"
repo MyApp.Repo
end
2023-08-16 19:58:48 +12:00
2023-03-24 10:59:48 +13:00
# If using policies, add the following bypass:
# policies do
# bypass AshAuthentication.Checks.AshAuthenticationInteraction do
# authorize_if always()
# end
# end
2022-12-08 15:23:47 +13:00
end
```
2022-12-09 11:32:34 +13:00
Lastly let's define our `User` resource, using password authentication and token
generation enabled.
2022-12-08 15:23:47 +13:00
```elixir
2024-04-08 00:03:01 +12:00
# lib/my_app/accounts/user.ex
2022-12-08 15:23:47 +13:00
defmodule MyApp.Accounts.User do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
2024-03-01 17:27:10 +13:00
extensions: [AshAuthentication],
2024-03-28 14:39:53 +13:00
authorizers: [Ash.Policy.Authorizer],
domain: MyApp.Accounts
2022-12-08 15:23:47 +13:00
attributes do
uuid_primary_key :id
2024-03-28 14:39:53 +13:00
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
2022-12-08 15:23:47 +13:00
end
authentication do
strategies do
2022-12-09 09:51:10 +13:00
password :password do
identity_field :email
end
2022-12-08 15:23:47 +13:00
end
tokens do
2024-05-14 15:04:45 +12:00
enabled? true
2022-12-08 15:23:47 +13:00
token_resource MyApp.Accounts.Token
2022-12-09 11:32:34 +13:00
signing_secret fn _, _ ->
Application.fetch_env(:my_app, :token_signing_secret)
end
2023-01-17 17:05:19 +13:00
end
2022-12-08 15:23:47 +13:00
end
postgres do
table "users"
repo MyApp.Repo
end
identities do
identity :unique_email, [:email]
end
2023-08-16 19:58:48 +12:00
2024-03-01 17:27:10 +13:00
# You can customize this if you wish, but this is a safe default that
# only allows user data to be interacted with via AshAuthentication.
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
policy always() do
forbid_if always()
end
end
2022-12-08 15:23:47 +13:00
end
```
Here we've added password authentication, using an email address as our
identifier.
Now we have enough in place to register and sign-in users using the
`AshAuthentication.Strategy` protocol.
2024-05-14 05:13:57 +12:00
## Token generation
If you have token generation enabled then you need to provide (at minimum) a
signing secret. As the name implies this should be a secret. AshAuthentication
provides a mechanism for looking up secrets at runtime using the
`AshAuthentication.Secret` behaviour. To save you a click, this means that you
can set your token signing secret using either a static string (please don't!),
a two-arity anonymous function, or a module which implements the
`AshAuthentication.Secret` behaviour.
At its simplest you should so something like this:
```elixir
# in lib/my_app/accounts/user.ex
signing_secret fn _, _ ->
Application.fetch_env(:my_app, :token_signing_secret)
end
```
Then, specify the secret token in the config file:
```elixir
# in config/config.exs
config :my_app, :token_signing_secret, "some_super_secret_random_value"
```
> ### The signing secret must not be committed to source control {: .warning}
>
> Proper management of secrets is outside the scope of this tutorial, but is
> absolutely crucial to the security of your application.
2022-12-08 15:23:47 +13:00
## Plugs and routing
2022-12-09 11:32:34 +13:00
If you're using Phoenix, then you can skip this section and go straight to
2023-01-30 13:17:55 +13:00
[Integrating Ash Authentication and Phoenix ](https://ash-hq.org/docs/guides/ash_authentication_phoenix/latest/tutorials/getting-started-with-ash-authentication-phoenix )
2022-12-08 15:23:47 +13:00
In order for your users to be able to sign in, you will likely need to provide
2024-03-28 14:39:53 +13:00
an HTTP endpoint to submit credentials or OAuth requests to. Ash Authentication
provides `AshAuthentication.Plug` for this purposes. It provides a `use` macro
2022-12-08 15:23:47 +13:00
which handles routing of requests to the correct providers, and defines
callbacks for successful and unsuccessful outcomes.
Let's generate our plug:
```elixir
# lib/my_app/auth_plug.ex
defmodule MyApp.AuthPlug do
use AshAuthentication.Plug, otp_app: :my_app
2023-02-07 09:35:31 +13:00
def handle_success(conn, _activity, user, token) do
2022-12-08 15:23:47 +13:00
if is_api_request?(conn) do
conn
|> send_resp(200, Jason.encode!(%{
authentication: %{
success: true,
token: token
}
}))
else
conn
|> store_in_session(user)
|> send_resp(200, EEx.eval_string("""
< h2 > Welcome back < %= @user .email %></ h2 >
""", user: user))
end
end
def handle_failure(conn, _activity, _reason) do
if is_api_request?(conn) do
conn
|> send_resp(401, Jason.encode!(%{
authentication: %{
success: false
}
}))
else
conn
|> send_resp(401, "< h2 > Incorrect email or password< / h2 > ")
end
end
defp is_api_request?(conn), do: "application/json" in get_req_header(conn, "accept")
end
```
Now that this is done, you can forward HTTP requests to it from your app's main
router using `forward "/auth", to: MyApp.AuthPlug` or similar.
Your generated auth plug module will also contain `load_from_session` and
`load_from_bearer` function plugs, which can be used to load users into assigns
based on the contents of the session store or `Authorization` header.
## Supervisor
AshAuthentication includes a supervisor which you should add to your
2024-03-28 14:39:53 +13:00
application's supervisor tree. This is used to run any periodic jobs related to
2022-12-08 15:23:47 +13:00
your authenticated resources (removing expired tokens, for example).
### Example
```elixir
defmodule MyApp.Application do
use Application
def start(_type, _args) do
children = [
2023-10-16 00:33:44 +13:00
# ...
# add this line -->
2022-12-08 15:23:47 +13:00
{AshAuthentication.Supervisor, otp_app: :my_app}
2023-10-16 00:33:44 +13:00
# < -- add this line
2022-12-08 15:23:47 +13:00
]
2023-10-16 00:33:44 +13:00
# ...
2022-12-08 15:23:47 +13:00
end
end
```
## Summary
In this guide we've learned how to install Ash Authentication, configure
resources and handle authentication HTTP requests.
You should now have an Ash application with working user authentication.
2023-01-30 13:17:55 +13:00
Up next, [Using with Phoenix ](https://ash-hq.org/docs/guides/ash_authentication_phoenix/latest/tutorials/getting-started-with-ash-authentication-phoenix ).