Guide: Customizing PhxAuthPlus
View SourceThis guide covers common customizations after generating authentication with
mix phx_auth_plus.gen.auth.
Table of Contents
- Adding Custom Fields to User
- Role-Based Authorization
- Configuring the Mailer
- Changing Password Requirements
- Disabling Account Confirmation
- Using API Tokens
- Switching Hashing Libraries
- 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
# lib/my_app/accounts/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)
end2. Add fields to the registration changeset
# lib/my_app/accounts/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)
end3. Add fields to the migration
# 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"
add :hashed_password, :string, null: false
add :confirmed_at, :naive_datetime
timestamps(type: :naive_datetime)
end4. Add fields to the registration form
<!-- 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
# lib/my_app/accounts/authorization.ex
defmodule MyApp.Accounts.Authorization do
alias MyApp.Accounts.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
end3. Create a require_admin plug
# lib/my_app_web/user_auth.ex
def require_admin_user(conn, _opts) do
if conn.assigns.current_user && conn.assigns.current_user.role == "admin" do
conn
else
conn
|> put_flash(:error, "You must be an admin to access this page.")
|> redirect(to: ~p"/")
|> halt()
end
end4. Use in router
pipeline :admin do
plug :require_authenticated_user
plug :require_admin_user
end
scope "/admin", MyAppWeb do
pipe_through [:browser, :admin]
# admin routes here
endConfiguring 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
# 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
endOther adapters
Swoosh supports many providers: SendGrid, Mailgun, Postmark, SES, etc. See Swoosh 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.7.21). To enforce them:
# lib/my_app/accounts/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)
endDisabling Account Confirmation
If you don't want email confirmation:
- Remove the
confirmed_atfield from the schema and migration - Remove the confirmation routes from the router
- Remove
confirm_user/1anddeliver_user_confirmation_instructions/2from Accounts - Remove
UserConfirmationLiveandUserConfirmationInstructionsLive - Remove the
:confirmtoken context fromUserToken
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:
# lib/my_app/accounts/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# 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_user, user)
else
_ -> conn |> put_status(:unauthorized) |> json(%{error: "unauthorized"}) |> halt()
end
endSwitching Hashing Libraries
You can switch at generation time:
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/accounts/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:
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.