この記事は、Elixir Advent Calendar 2023 シリーズ14 の16日目です
piacere です、ご覧いただいてありがとございます
今年後半にαリリースした、過去/現在/未来のスキルから、あなたのBright(輝き)とRight(正しさ)を引き出すプロダクト「Bright」の実装から、Elixirプロダクション開発のヒントをお届けするシリーズです
前回は、Elixir Web CRUDの基本として、LiveView Scaffold+Tailwindで「スキルパネル」そっくりさん(下記赤枠)を爆速開発 する内容でした
今回は、Phoenix標準の独自ID認証機能「phx_gen_auth」と、SNS認証(Google/GitHub/Twitterなどのアカウントでの認証)を叶えるライブラリ「Überauth」で、Bright「ログイン」そっくりさんを作ってみましょう
なお、Brightが生まれた背景は、昨年Elixir Advent Calendarで下記にまとめています(400いいねを超える人気コラム)
あと、このコラムが、面白かったり、役に立ったら、 をお願いします
「phx_gen_auth」による独自ID認証はコマンド1発
phx_gen_authを使うと、認証処理用テーブルとユーザー登録/ログイン画面のScaffold自動生成ができます
なお、実際のBrightにおける各テーブルIDは、UUIDを利用していますが、ここでは簡便化のためにintegerで定義します
今回は、前回作ったPhoenix PJに機能追加していく形で進行しますので、作成が未だであれば、前回コラムをお試しいただいて から、本コラムに戻って下記を実施してください
認証をScaffold自動生成
早速、Scaffoldしてみましょう(Phoenix起動まで一気に行います)
mix phx.gen.auth Accounts User users
mix deps.get
mix ecto.migrate
iex -S mix phx.server
routerは勝手に追加されるので、手動追加不要です(エンドポイントを変更したり、一部塞ぎたい場合は手動で行ってください)
ブラウザで http://localhost:4000/
にアクセスすると、右上に「Register」「Log in」リンクが追加されるので、「Register」でユーザー登録してみましょう
ユーザー登録が行われ、ログイン済み状態に右上リンクが変わるので、「Log out」し、「Log in」リンクを押下してみましょう
先ほど作成したユーザーでログインできることが確認できると思います
このようにphx_gen_authは、独自ID認証をとてもカンタンに実現してくれる強力なScaffoldです
ユーザー登録時にメール確認を挟む
上記で、ユーザー登録時にメールで確認を行いませんでしたが、実は確認のためのURLが載ったメールは投げられており、ブラウザで http://localhost:4000/dev/mailbox
をご覧ください
このメールを実際に投げれるようにしたり、メール本文をカスタマイズしたい場合は、下記コラムのSwoosh部分をご参考ください
あとは、ユーザー登録と同時にログインさせないようにするだけですが、こちらは下記2箇所のコード改修だけで実現できます
defmodule Bright.Accounts.User do
…
- def valid_password?(%Bright.Accounts.User{hashed_password: hashed_password}, password)
+ def valid_password?(%Bright.Accounts.User{hashed_password: hashed_password, confirmed_at: confirmed_at}, password)
- when is_binary(hashed_password) and byte_size(password) > 0 do
+ when is_binary(hashed_password) and byte_size(password) > 0 and is_nil(confirmed_at) == false do
Bcrypt.verify_pass(password, hashed_password)
end
…
defmodule BrightWeb.UserSessionController do
…
def create(conn, %{"_action" => "registered"} = params) do
- create(conn, params, "Account created successfully!")
+ conn
+ |> put_flash(:info, "Please log in after accessing the registration confirmation URL in the e-mail.")
+ |> redirect(to: ~p"/users/log_in")
end
…
1つ目のコードで、users
テーブルの confirmed_at
が入っていないときはログイン不可にでき、メールで送られたURLを踏むと、confirmed_at
に日時が入って、ログイン可能となる仕組みです
2つ目のコードで、ユーザー登録直後をアカウント作成完了とせず、ログイン画面で登録確認メールのチェックを促していますが、もしこのコードを入れないと、下記 if
式で confirmed_at
が入っておらずfalseで else
の「Invalid email or password」表示が行われ、ユーザーが混乱するので対応をお忘れなく
defmodule BrightWeb.UserSessionController do
…
defp create(conn, %{"user" => user_params}, info) do
%{"email" => email, "password" => password} = user_params
if user = Accounts.get_user_by_email_and_password(email, password) do
…
else
# In order to prevent user enumeration attacks, don't disclose whether the email is registered.
conn
|> put_flash(:error, "Invalid email or password")
|> put_flash(:email, String.slice(email, 0, 160))
|> redirect(to: ~p"/users/log_in")
end
…
メールのURLにアクセスすると、下記画面になり、ボタン押下後にログインが可能となるので、試してみてください
オマケ:バリデーションのカスタマイズ
デフォルトのphx_gen_authは、パスワード最低長が12文字と長いため、カスタマイズするときは下記コラムを参考にしてください
「Überauth」によるSNS認証
Überauthを使うと、さまざまなSNSアカウントでの認証をカンタンに実装できるようになります
今回は、そのGoogleアカウント向けライブラリである下記を使います
事前準備:ログイン画面に「Googleログイン」ボタン追加
その前に、phx_gen_authのログイン画面に、「Googleログイン」ボタンを追加しておきましょう
phx_gen_authがどのコンポーネントで定義されているかは、LiveView 0.20で使えるようになった下記の技を使ってブラウザ上から特定してみましょう
ログイン画面が、下記コード/行数で定義されていることが、いとも容易く特定できました
試しに、「Googleログイン」ボタンを追加してみましょう
defmodule BrightWeb.UserLoginLive do
…
<:actions>
<.button phx-disable-with="Signing in..." class="w-full">
Sign in <span aria-hidden="true">→</span>
</.button>
</:actions>
</.simple_form>
+ <p class="py-4">
+ <.link href={~p"/auth/google"} class="w-full ">
+ <.button class="w-full bg-gray-100 bg-[url('https://app.bright-fun.org/images/bg_google.png')] bg-left bg-no-repeat border border-solid border-black hover:opacity-40">
+ <span class="text-black">Googleログイン</span>
+ </.button>
+ </.link>
+ </p>
…
これまでは、画面文言などから推定していたのが、LiveViewフロントデバッグのおかげで嘘のようにカンタンです
Überauthの導入
Überauth Googleのインストール含む各種設定を入れます
defmodule Bright.MixProject do
…
defp deps do
[
+ {:ueberauth_google, "~> 0.10"},
{:bcrypt_elixir, "~> 3.0"},
{:phoenix, "~> 1.7.10"},
…
# This file is responsible for configuring your application
…
import_config "#{config_env()}.exs"
+config :ueberauth, Ueberauth,
+ providers: [
+ google: {Ueberauth.Strategy.Google, []}
+ ]
+
+config :ueberauth, Ueberauth.Strategy.Google.OAuth,
+ client_id: {System, :get_env, ["GOOGLE_CLIENT_ID"]},
+ client_secret: {System, :get_env, ["GOOGLE_CLIENT_SECRET"]}
Überauthが処理するSNS認証(内部的にはOAuth2)を受け付けるコントローラを作り、routerを通します
Überauthでは、ueberauth_auth
と ueberauth_failure
の2つのハンドラを作るだけでSNS認証実装が済んでしまうので、とても楽チンです
defmodule BrightWeb.OauthController do
use BrightWeb, :controller
plug Ueberauth
alias Ueberauth.Strategy.Helpers
def request(conn, _params), do: nil
def callback(%{assigns: %{ueberauth_auth: %Ueberauth.Auth{} = auth}} = conn, _params) do
conn
|> put_flash(:info, "Succeed to authenticate!")
|> put_session(:current_user, auth.uid)
|> configure_session(renew: true)
|> redirect(to: "/")
end
def callback(%{assigns: %{ueberauth_failure: %Ueberauth.Failure{} = _failure}} = conn, _params) do
conn
|> put_flash(:error, "Failed to authenticate.")
|> redirect(to: "/")
end
end
defmodule BrightWeb.Router do
…
pipeline :api do
plug :accepts, ["json"]
end
+ pipeline :auth do
+ plug Ueberauth
+ end
+
+ scope "/auth", BrightWeb do
+ pipe_through [:browser, :auth]
+
+ get "/:provider", OauthController, :request
+ get "/:provider/callback", OauthController, :callback
+ post "/:provider/callback", OauthController, :callback
+ end
…
ここまで準備できたら、インストールと起動を行います
mix deps.get
iex -S mix phx.server
ブラウザで「Googleログイン」ボタンを押下して、下記画面になれば、Elixir側のSNS認証実装は成功です
OAuthクライアントIDを取得してSNS認証を動かす
上記エラーは、クライアントIDがおかしいことで起こっている訳ですが、それもそのはず、GOOGLE_CLIENT_ID
と GOOGLE_CLIENT_SECRET
が現時点では空だからです
Googleアカウント認証用のクライアントIDを発行する必要があるので、任意のGoogle CloudアカウントでGoogle Cloudに行き、「APIとサービス」「認証情報」を選択してください
「+認証情報を作成」から「OAuthクライアントID」を選択します
作成後クライアントIDのリンクをクリックすると、下記画面が開くので、「クライアントID」と「クライアントシークレット」を控えます
ターミナルから下記のように設定します
export GOOGLE_CLIENT_ID=【ここにクライアントIDを記入】
export GOOGLE_CLIENT_SECRET=【ここにクライアントシークレットを記入】
上記設定後、再度、ブラウザから「Googleログイン」ボタンを押すと、下記画面に変わるので、Googleアカウントを入力してみてください(すでにGoogleアカウント利用中だとログイン済みアカウントがリストされるのでアカウント選択してください)
すると、Googleアカウントでログインでき … たように見えて、ログイン済み状態にはなっていません
それもそのはず、SNS認証を通ったからといって、Elixirで作られたシステムの認証(phx_gen_auth)を通過している訳では無いからです
とは言え、Überauthを使うと、SNS認証の実装がこんなにお手軽にに済むので、とても素敵なライブラリです
この後は?+α(プロダクション開発のリアル)
SNS認証後に、phx_gen_authユーザー登録 or ログインをさせる処理を追加します
つまり、SNS認証から来たユーザーが初回であればユーザー登録し、既に存在しているユーザーであればログインさせます
これの実装は、前回やったScaffoldを使い、GoogleアカウントなどのSNS認証アカウントとphx_gen_authユーザーを紐付けるテーブルを設けます
そして、上記 ueberauth_auth
を通る際、phx_gen_authユーザーと紐付いていればログイン、紐付いていなければphx_gen_authユーザー登録させることになります
紐付けるテーブルは、こんな感じでScaffoldします
mix phx.gen.live Accounts UserSocialAuth user_social_auths provider:string identifier:string token:binary display_name:string user_id:references:users
provider
には認証プロバイダ(Google/GitHub/Twitterなど)、identifier
にはプロバイダから返ってきたuid、token
にはプロバイダから返ってきたアクセストークン、display_name
にはプロバイダ側で利用しているメールアドレス等、user_id
には紐付くphx_gen_authユーザーを入れます
phx_gen_authのユーザー登録やログインは、下記で行っているので、これらを呼び出すか、相当処理を行います(このあたりも余力あればLTなどで共有します)
関数名 | 処理内容 |
---|---|
Accounts.register_user |
SNS認証後のphx_gen_authユーザー登録 |
UserAuth.log_in_user |
SNS認証後のphx_gen_authログイン |
Accounts.generate_user_session_token |
SNS認証後のphx_gen_authトークン発行 |
あと、デザインをBright「ログイン」に寄せていくなら、前回やったTailwindを活用して近付けてみてください
なおBrightには、この後、2段階認証やユーザー設定からのSNS認証解除、サブメールアドレス追加など、ここで語り尽くせない様々な認証関連機能があるため、実際にプロダクション開発する際は、これら機能の検討や見積も忘れないように注意してください
また、Überauthは単体テストが揃っていないため、プロダクション開発時には、Bright認証の実装者でもある @koyo-miyamura さんの下記コラムをぜひ実践してください
ここまでで習得したスキルは?
さて、Bright「ログイン」そっくりさんを作る過程で、下記のスキルを習得したことになります
- 「Elixir入門」
- クラス1:個人で簡単なWeb+DBアプリが作れる
- mix deps.get
- Router(router.ex)追加/変更
- mix phx.gen.live(scaffold)/ブラウザ上CRUD操作
- scaffold生成.html.heexの.form/.input追加/改修
- HTML
- クラス2:Web+DBアプリ開発チームに参画できる
- config追加/変更
- ガード節
- Changeset/バリデーション変更 ※オマケを実施していたら
- クラス3:サポート有ならWeb+DBチーム開発できる
- phx_gen_auth
- クラス1:個人で簡単なWeb+DBアプリが作れる
- 「Webアプリ開発 Elixir」
- クラス1:零細Web開発をサポート無しでこなせる
- Uberauth(ソーシャルログイン)
- クラス1:零細Web開発をサポート無しでこなせる
https://bright-fun.org
](https://bright-fun.org
ぜひ下記からBrightを無料ユーザー登録して、スキルパネル「Elixir入門」「Webアプリ開発 Elixir」に、ここまで習得したスキルに「●」をスキル入力してみてください
イマイチ内容とスキルが結びついていない方も、Brightを使いながら、該当スキルを調べたり、復習をしてみてください