はじめに
この記事はElixirアドベントカレンダー2025シリーズ2の15日目の記事です。
Phoenix 1.8になって色々便利になったのでそれに合わせて新しく書き直していこうと思います
PhoenixのAPIサーバーとElixirDesktopをクライアントとしたクラサバ構成の構築と
Phoenix1.8で新しくなった標準認証機能について解説します
APIサーバーの作成
以下のコマンドでプロジェクトを作成し、DBも作成します
mix phx.new blog
cd blog
mix ecto.create
認証機能の実装
認証機能を実装していきます
ジェネレーターの実行
Phoenixにはビルドインで認証機能のジェネレーターが搭載されており、以下の構文で実行できます
mix phx.gen.auth [コンテキスト名] [スキーマ名] [テーブル名]
上記のコマンドで以下の機能が構築されます
ユーザーの新規登録
ユーザーのログイン、ログアウト
メールアドレス、パスワードの変更
セッション管理
認証・非認証時のルーティング処理
これらはすべてコードとして生成されるので、理解・改修が非常に楽です
mix phx.gen.auth Accounts User users
主に以下のファイルが生成されます
- accounts.ex -> ユーザーの取得、メール、パスワード等の更新処理
- user_notifier.ex -> 確認メール、パスワードリセット等を送信する設定・文面
- user_token.ex -> セッション等で使用される認証トークン関連の処理
- user.ex -> スキーマとバリデーション
- scope.ex -> **1.8新機能** 認可周りの処理を書く場所
- user_auth.ex -> 認証、セッション、ルーティング周り
- user_session_conftoller.ex -> ログイン時、新規登録時のセッション処理
LiveView周りはファイルが減ってスッキリしています
1.7系
- user_confirmation_instructions_live.ex ->確認メール再送画面
- user_confirmation_live.ex -> 本人確認画面
- user_forgot_password_live.ex -> パスワードリセットメール送信画面
- user_login_live.ex -> ログイン画面
- user_registration_live.ex -> 新規登録画面
- user_reset_password_live.ex -> パスワードリセット画面
- user_settings_live.ex -> パスワード、メールアドレス変更画面
1.8系
- confirmation.ex -> パスワードレス認証のマジックリンクのトークンチェック
- login.ex -> メールアドレス&パスワードでのログインとマジックリンクからのログイン
- regstration.ex -> メールアドレスを送信してマジックリンクでのユーザー登録
- settings.ex -> メールアドレスの変更とパスワード設定
これ以外はマイグレーションファイルやテストコードが生成されます
あとはAGENTS.mdに認証周りの記述が追加されます
認証周りの変更
scopeという概念が入ります主にロールごとにデータを付け加えたり、できることを設定したりということを想定しています
例としてはスーパーユーザーが挙げられ、パスワード等センシティブなデータを変更する場合に再度認証する(email&password or magic link)という処理を挟んだりします
gen.authした際に、scopeに関する記述がconfig.exsに以下のように追加されています
元々は assigns.current_userでログインしているユーザーを取得しいましたが
そこがcurrent_scopeになってその下に userが入るようになっています
config :blog, :scopes,
user: [
default: true,
module: Blog.Accounts.Scope,
assign_key: :current_scope,
access_path: [:user, :id],
schema_key: :user_id,
schema_type: :id,
schema_table: :users,
test_data_fixture: Blog.AccountsFixtures,
test_setup_helper: :register_and_log_in_user
]
他の大きな変更は登録時にメールアドレス+パスワードだったのが、入力した認証トークン付きURLを送信してそのURLを開いて認証するマジックリンク形式のユーザー登録がデフォルトになりました
必要なら設定ページでパスワードを登録してねという感じです
どうせパスワードマネージャーにいれたり、忘れたり、漏れたりするんで
ワンタイムパスワード付きURLでやれば登録ハードルも低いで運営が楽といえば楽です
というわけで以下のコマンドを実行します
mix phx.gen.auth Accounts User users
emailのnull許可
ユーザー登録時にメールアドレスもなしのゲストユーザー的なものをしたいのでemailのnull許可をします
create table(:users) do
- add :email, :citext, null: false
+ add :email, :citext
add :hashed_password, :string
add :confirmed_at, :utc_datetime
timestamps(type: :utc_datetime)
end
暗号化ライブラリの変更
ジェネレーターの実行時に依存ライブラリのリストのmix.exsのdeps/0関数にbcrypt_elixirが追加されていますが、こちらはネイティブのBcryptを使用する関係上iOS、Androidでは動作しないため Pbkdf2のElixir実装であるpbkdf2_elixirに変更します
defp deps do
[
- {:bcrypt_elixir, "~> 3.0"},
+ {:pbkdf2_elixir, "~> 2.0"},
...
]
end
変更したら以下のコマンドでライブラリを追加後DBへマイグレーションを実行します
mix deps.get
mix ecto.migrate
パスワードハッシュで使用している箇所をpbkdf2に置き換えます
defp maybe_hash_password(changeset, opts) do
hash_password? = Keyword.get(opts, :hash_password, true)
password = get_change(changeset, :password)
if hash_password? && password && changeset.valid? do
changeset
- # If using Bcrypt, then further validate it is at most 72 bytes long
+ # If using Pbkdf2, then further validate it is at most 72 bytes long
|> validate_length(:password, max: 72, count: :bytes)
# Hashing could be done with `Ecto.Changeset.prepare_changes/2`, but that
# would keep the database transaction open longer and hurt performance.
- |> put_change(:hashed_password, Bcrypt.hash_pwd_salt(password))
+ |> put_change(:hashed_password, Pbkdf2.hash_pwd_salt(password))
|> delete_change(:password)
else
changeset
end
end
パスワードの一致検証の箇所にあるので差し替えます
@doc """
Verifies the password.
If there is no user or the user doesn't have a password, we call
- `Bcrypt.no_user_verify/0` to avoid timing attacks.
+ `Pbkdf2.no_user_verify/0` to avoid timing attacks.
"""
def valid_password?(%Trarecord.Accounts.User{hashed_password: hashed_password}, password)
when is_binary(hashed_password) and byte_size(password) > 0 do
- Bcrypt.verify_pass(password, hashed_password)
+ Pbkdf2.verify_pass(password, hashed_password)
end
def valid_password?(_, _) do
- Bcrypt.no_user_verify()
+ Pbkdf2.no_user_verify()
false
end
テスト実行時の設定もあるのでそちらも変更します
import Config
# Only in tests, remove the complexity from the password hashing algorithm
- config :bcrypt_elixir, :log_rounds, 1
+ config :pbkdf2_elixir, :log_rounds, 1
最後に
APIサーバーのプロジェクトの作成とユーザー認証機能を作成しました
認証機能に関してはAPIサーバーだけ使うならrouter.exからリンクを消しても良いですし
Webダッシュボードとか作るように残しておくとかも良いかもしれませんね
本記事は以上になりますありがとうございました
次はセッション周りの実装になります