5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ElixirDesktopで作るブログアプリ APIサーバー作成・ユーザー認証

Last updated at Posted at 2025-12-16

はじめに

この記事は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許可をします

priv/repo/migrations/20251216132814_create_users_auth_tables.exs
    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に変更します

mix.exs:L44
  defp deps do
    [
-     {:bcrypt_elixir, "~> 3.0"},
+     {:pbkdf2_elixir, "~> 2.0"},
      ...
    ]
  end

変更したら以下のコマンドでライブラリを追加後DBへマイグレーションを実行します

mix deps.get
mix ecto.migrate

パスワードハッシュで使用している箇所をpbkdf2に置き換えます

lib/blog/accounts/user.ex:L92
  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

パスワードの一致検証の箇所にあるので差し替えます

lib/blog/accounts/user.ex:L117
  @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

テスト実行時の設定もあるのでそちらも変更します

config/test.exs
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ダッシュボードとか作るように残しておくとかも良いかもしれませんね

本記事は以上になりますありがとうございました

次はセッション周りの実装になります

5
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?