Packages
Complete authentication generator for Phoenix 1.8+. Email + password and magic link login, registration, password reset, email confirmation, settings, and scope-based auth — aligned with the Phoenix 1.8.9 phx.gen.auth templates.
Current section
Files
Jump to
Current section
Files
phx_auth_plus
GUIDE.md
GUIDE.md
# Guide: Customizing PhxAuthPlus
This guide covers common customizations after generating authentication with
`mix phx_auth_plus.gen.auth`.
## Table of Contents
- [Adding Custom Fields to User](#adding-custom-fields-to-user)
- [Role-Based Authorization](#role-based-authorization)
- [Configuring the Mailer](#configuring-the-mailer)
- [Changing Password Requirements](#changing-password-requirements)
- [Disabling Account Confirmation](#disabling-account-confirmation)
- [Using API Tokens](#using-api-tokens)
- [Switching Hashing Libraries](#switching-hashing-libraries)
- [Umbrella Project Support](#umbrella-project-support)
---
## Adding Custom Fields to User
The generated `User` schema has `email`, `hashed_password`, and `confirmed_at`.
To add custom fields (e.g., `first_name`, `role`):
### 1. Add fields to the schema
```elixir
# lib/my_app/user.ex
schema "users" do
field :email, :string
field :first_name, :string
field :last_name, :string
field :role, :string, default: "user"
field :password, :string, virtual: true, redact: true
field :hashed_password, :string, redact: true
field :current_password, :string, virtual: true, redact: true
field :confirmed_at, :naive_datetime
timestamps(type: :naive_datetime)
end
```
### 2. Add fields to the registration changeset
```elixir
# lib/my_app/user.ex
def registration_changeset(user, attrs, opts \\ []) do
user
|> cast(attrs, [:email, :password, :first_name, :last_name])
|> validate_required([:email, :password, :first_name, :last_name])
|> validate_email()
|> validate_password(opts)
|> maybe_hash_password(opts)
end
```
### 3. Add fields to the migration
```elixir
# priv/repo/migrations/*_create_users_auth_tables.exs
create table(:users) do
add :email, :string, null: false
add :first_name, :string
add :last_name, :string
add :role, :string, default: "user"
# hashed_password is nullable to support magic link users without a password
add :hashed_password, :string
add :confirmed_at, :naive_datetime
timestamps(type: :naive_datetime)
end
```
### 4. Add fields to the registration form
```heex
<!-- lib/my_app_web/user_registration_live.ex -->
<.input field={@form[:first_name]} type="text" label="First name" />
<.input field={@form[:last_name]} type="text" label="Last name" />
```
---
## Role-Based Authorization
### 1. Add a `role` field (see above)
### 2. Create an authorization module
```elixir
# lib/my_app/accounts/authorization.ex
defmodule MyApp.Accounts.Authorization do
alias MyApp.User
def admin?(%User{role: "admin"}), do: true
def admin?(_), do: false
def can?(%User{role: "admin"}, _), do: true
def can?(%User{role: "user"}, :read), do: true
def can?(_, :read), do: true
def can?(_, _), do: false
end
```
### 3. Create a require_admin plug
```elixir
# lib/my_app_web/user_auth.ex
def require_admin_user(conn, _opts) do
if conn.assigns.current_scope && conn.assigns.current_scope.user.role == "admin" do
conn
else
conn
|> put_flash(:error, "You must be an admin to access this page.")
|> redirect(to: ~p"/")
|> halt()
end
end
```
### 4. Use in router
```elixir
pipeline :admin do
plug :require_authenticated_user
plug :require_admin_user
end
scope "/admin", MyAppWeb do
pipe_through [:browser, :admin]
# admin routes here
end
```
---
## Configuring the Mailer
The generated `UserNotifier` uses Swoosh to send emails. By default in dev,
emails are captured by the local adapter and visible at `/dev/mailbox`.
### Production with SMTP
```elixir
# config/runtime.exs
if config_env() == :prod do
config :my_app, MyApp.Mailer,
adapter: Swoosh.Adapters.SMTP,
relay: "smtp.example.com",
username: "noreply@example.com",
password: System.get_env("SMTP_PASSWORD"),
port: 587,
tls: :always
end
```
### Other adapters
Swoosh supports many providers: SendGrid, Mailgun, Postmark, SES, etc.
See [Swoosh adapters](https://hexdocs.pm/swoosh/Swoosh.html#module-adapters).
---
## Changing Password Requirements
The generated `password_changeset` validates:
- Minimum 12 characters
- Maximum 72 characters (bcrypt limit)
- At least one lowercase, one uppercase, and one special character or number
These are commented out by default (as in Phoenix 1.8.9). To enforce them:
```elixir
# lib/my_app/user.ex
defp validate_password(changeset, opts) do
changeset
|> validate_required([:password])
|> validate_length(:password, min: 12, max: 72)
# Uncomment to enforce complexity:
|> validate_format(:password, ~r/[a-z]/, message: "at least one lower case character")
|> validate_format(:password, ~r/[A-Z]/, message: "at least one upper case character")
|> validate_format(:password, ~r/[!?@#$%^&*_0-9]/, message: "at least one digit or punctuation character")
|> maybe_hash_password(opts)
end
```
---
## Disabling Account Confirmation
If you don't want email confirmation:
1. Remove the `confirmed_at` field from the schema and migration
2. Remove the confirmation routes from the router
3. Remove `confirm_user/1` and `deliver_user_confirmation_instructions/2` from Accounts
4. Remove `UserConfirmationLive`
5. Remove the `:confirm` token context from `UserToken`
Or simply: generate with confirmation, then remove what you don't need.
The confirmation flow is isolated enough to remove cleanly.
---
## Using API Tokens
PhxAuthPlus generates session-based auth for browsers. For API tokens,
you can extend the `UserToken` schema:
```elixir
# lib/my_app/user_token.ex
def build_api_token(user) do
{token, user_token} = UserToken.build_hashed_token(user, "api")
Repo.insert!(user_token)
token
end
def verify_api_token_query(token) do
case UserToken.verify_hashed_token_query(token, "api") do
{:ok, query} -> {:ok, query}
:error -> :error
end
end
```
```elixir
# lib/my_app_web/api_auth.ex
def authenticate_api(conn, _opts) do
with ["Bearer " <> token] <- get_req_header(conn, "authorization"),
{:ok, user} <- Accounts.get_user_by_api_token(token) do
assign(conn, :current_scope, %MyApp.Scope{user: user})
else
_ -> conn |> put_status(:unauthorized) |> json(%{error: "unauthorized"}) |> halt()
end
end
```
---
## Switching Hashing Libraries
You can switch at generation time:
```bash
mix phx_auth_plus.gen.auth Accounts User users --hashing-lib argon2
```
To switch after generation, change the hashing module calls in:
- `lib/my_app/user.ex` (`hash_pwd_salt`, `verify_pass`, `no_user_verify`)
- Update the dependency in `mix.exs`
---
## Umbrella Project Support
For umbrella projects, use `--context-app`:
```bash
mix phx_auth_plus.gen.auth Accounts User users --context-app my_app_core
```
This generates the context and schemas in `my_app_core` and the web files
in the current web app.