Skip to content

Commit

Permalink
WIP on igniter installer
Browse files Browse the repository at this point in the history
  • Loading branch information
zachdaniel committed Aug 2, 2024
1 parent f6c2ae6 commit 17fbf0b
Show file tree
Hide file tree
Showing 10 changed files with 310 additions and 157 deletions.
4 changes: 2 additions & 2 deletions .tool-versions
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
elixir 1.15.6
erlang 26.1
elixir 1.17.2-otp-27
erlang 27.0
5 changes: 4 additions & 1 deletion documentation/tutorials/confirmation.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Confirmation Tutorial

This is a quick tutorial on how to configure your application to enable 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.

In this tutorial we'll assume that you have a `User` resource which uses `email` as it's user identifier. We'll show you how to confirm a new user on sign-up and also require them to confirm if they wish to change their email address.

Expand Down Expand Up @@ -56,6 +58,7 @@ defmodule MyApp.Accounts.User do
end
end
```

Next we will have to generate and run migrations to add confirmed_at column to user resource

```bash
Expand Down
211 changes: 69 additions & 142 deletions documentation/tutorials/get-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,19 @@ 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
<!-- tabs-open -->

### Using Igniter (recommended)

#### Install the extension

```sh
mix igniter.install ash_authentication
```

### Manual

#### Add to your application's dependencies

Bring in the `ash_authentication` dependency:

Expand All @@ -29,66 +41,10 @@ And add `ash_authentication` to your `.formatter.exs`:
]
```

## Choosing your extensions, strategies and add-ons

Ash Authentication supports many different features, each configured separately.

### `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.

The `AshAuthentication` extension provides configuration and sensible defaults for settings which relate to authentication, regardless of authentication mechanism.

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
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
define either or both of these actions yourself if you wish to customise them -
if you do so then the extension will do its best to validate that all required
configuration is present.

The `AshAuthentication.Strategy.Password` DSL allows you to override any of the default values.

### `AshAuthentication.Strategy.OAuth2`

This authentication strategy provides registration and sign-in for users using a
remote [OAuth 2.0](https://oauth.net/2/) server as the source of truth. You
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
information about tokens that can't be encoded into the tokens themselves. A
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
this extension enabled. It is used to keep track of the links between your
local user records and their many remote identities.
## Example
#### Create authentication domain and resources

Let's create an `Accounts` domain in our application which provides a `User`
resource and a `Token` resource.
resource and a `Token` resource. This tutorial is assuming that you are using `AshPostgres`.
First, let's define our domain:

Expand Down Expand Up @@ -126,41 +82,63 @@ defmodule MyApp.Repo do
use AshPostgres.Repo, otp_app: :my_app

def installed_extensions do
["ash-functions", "uuid-ossp", "citext"]
["ash-functions", "citext"]
end
end
```

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.
#### Setup Token Resource

```elixir
# lib/my_app/accounts/token.ex

defmodule MyApp.Accounts.Token do
use Ash.Resource,
data_layer: AshPostgres.DataLayer,
extensions: [AshAuthentication.TokenResource],
# If using policies, enable the policy authorizer:
# authorizers: [Ash.Policy.Authorizer],
authorizers: [Ash.Policy.Authorizer],
domain: MyApp.Accounts

postgres do
table "tokens"
repo MyApp.Repo
end

# If using policies, add the following bypass:
# policies do
# bypass AshAuthentication.Checks.AshAuthenticationInteraction do
# authorize_if always()
# end
# end
policies do
bypass AshAuthentication.Checks.AshAuthenticationInteraction do
authorize_if always()
end
end
end
```

#### Supervisor

AshAuthentication includes a supervisor which you should add to your
application's supervisor tree. This is used to run any periodic jobs related to
your authenticated resources (removing expired tokens, for example).

### Example

```elixir
defmodule MyApp.Application do
use Application

def start(_type, _args) do
children = [
# ...
# add this line -->
{AshAuthentication.Supervisor, otp_app: :my_app}
# <-- add this line
]
# ...
end
end
```

Lastly let's define our `User` resource, using password authentication and token
generation enabled.
Lastly let's define our `User` resource. Note that we aren't defining any authentication strategies here.
This setup is used for all strategies. Once you have done this, you can follow one of the strategy specific
guides at the bottom of this page.

```elixir
# lib/my_app/accounts/user.ex
Expand All @@ -174,21 +152,14 @@ defmodule MyApp.Accounts.User do

attributes do
uuid_primary_key :id
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
end

authentication do
strategies do
password :password do
identity_field :email
end
end

tokens do
enabled? true
token_resource MyApp.Accounts.Token
signing_secret fn _, _ ->
# This is a secret key used to sign tokens. See the note below on secrets management
Application.fetch_env(:my_app, :token_signing_secret)
end
end
Expand All @@ -199,10 +170,6 @@ defmodule MyApp.Accounts.User do
repo MyApp.Repo
end

identities do
identity :unique_email, [:email]
end

# 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
Expand All @@ -217,47 +184,31 @@ defmodule MyApp.Accounts.User do
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.

## 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:
> ### 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.
```elixir
# in lib/my_app/accounts/user.ex
#### Choose your strategies and add-ons

signing_secret fn _, _ ->
Application.fetch_env(:my_app, :token_signing_secret)
end
```
##### Strategies

Then, specify the secret token in the config file:
- [Password](/documentation/tutorials/password.md)
- [Github](/documentation/tutorials/github.md)
- [Google](/documentation/tutorials/google.md)
- [Magic Links](/documentation/tutorials/magic-links.md)
- [Auth0](/documentation/tutorials/auth-0.md)
- Open ID: `AshAuthentication.Strategy.Oidc`
- OAuth2: `AshAuthentication.Strategy.OAuth2`

```elixir
# in config/config.exs
config :my_app, :token_signing_secret, "some_super_secret_random_value"
```
##### Add-Ons

> ### 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.
- [Confirmation](/documentation/tutorials/confirmation.md): confirming changes to user accounts (i.e via email)
- UserIdentity: `AshAuthentication.UserIdentity`: supporting multiple social sign on identities & refreshing tokens

## Plugs and routing
#### Set up your phoenix or plug application

If you're using Phoenix, then you can skip this section and go straight to
If you're using Phoenix, skip this section and go to
[Integrating Ash Authentication and Phoenix](https://ash-hq.org/docs/guides/ash_authentication_phoenix/latest/tutorials/getting-started-with-ash-authentication-phoenix)

In order for your users to be able to sign in, you will likely need to provide
Expand Down Expand Up @@ -317,30 +268,6 @@ 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
application's supervisor tree. This is used to run any periodic jobs related to
your authenticated resources (removing expired tokens, for example).

### Example

```elixir
defmodule MyApp.Application do
use Application

def start(_type, _args) do
children = [
# ...
# add this line -->
{AshAuthentication.Supervisor, otp_app: :my_app}
# <-- add this line
]
# ...
end
end
```

## Summary

In this guide we've learned how to install Ash Authentication, configure
Expand Down
55 changes: 55 additions & 0 deletions documentation/tutorials/password.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
# Password Authentication

## Add Bcrypt To your dependencies

This step is not strictly necessary, but in the next major version of `AshAuthentication`,
`Bcrypt` will be an optional dependency. This will make that upgrade slightly easier.

```elixir
{:bcrypt_elixir, "~> 3.0"}
```

## Add Attributes

Add an `email` (or `username`) and `hashed_password` attribute to your user resource.

```elixir
# lib/my_app/accounts/user.ex
attributes do
...
attribute :email, :ci_string, allow_nil?: false, public?: true
attribute :hashed_password, :string, allow_nil?: false, sensitive?: true
end
```

Ensure that the `email` (or username) is unique.

```elixir
# lib/my_app/accounts/user.ex
identities do
identity :unique_email, [:email]
# or
identity :unique_username, [:username]
end
```

## Add the password strategy

Configure it to use the `:email` or `:username` as the identity field.

```elixir
# lib/my_app/accounts/user.ex
authentication do
...
strategies do
password :password do
identity_field :email
# or
identity_field :username
end
end
end
```

Now we have enough in place to register and sign-in users using the
`AshAuthentication.Strategy` protocol.
8 changes: 7 additions & 1 deletion lib/ash_authentication/strategies/oauth2.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,13 @@ defmodule AshAuthentication.Strategy.OAuth2 do
alias __MODULE__.{Dsl, Transformer, Verifier}

@moduledoc """
Strategy for authenticating using an OAuth 2.0 server as the source of truth.
Strategy for authenticating using any OAuth 2.0 server as the source of truth.
This authentication strategy provides registration and sign-in for users using a
remote [OAuth 2.0](https://oauth.net/2/) server as the source of truth. You
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.
This strategy wraps the excellent [`assent`](https://hex.pm/packages/assent)
package, which provides OAuth 2.0 capabilities.
Expand Down
Loading

0 comments on commit 17fbf0b

Please sign in to comment.