Stories

Detail Return Return

Phoenix:支持 Google 登錄 - Stories Detail

感謝 chriis 的工作,文章寫得很好。
但是我在實踐的過程中,遇到了一些問題,也輾轉最終解決了。我將記錄下實踐過程,以我的風格。

要求:本地有一個可以跑得通的 Phoenix 應用(不要求必須得用LiveView),並且已經有了可用的身份驗證系統(用户系統)

一些創建和配置工作


到 Google 開發者後台,創建一個新的項目,或者選擇已有項目。
image.png

  1. 點擊進入到剛才創建的項目主頁
  2. 點擊左側菜單欄中的「OAuth 權限請求頁面」
  3. 點擊左側菜單欄中的「菜單」-> 「開始」,進入到創建應用頁面,完成基本信息的填寫。

image.png

  1. 點擊「創建 OAuth 客户端」,填寫基本信息。這裏有兩個重要字段需要填充:
    image.png
  • 「已獲授權的 JavaScript 來源」填你項目的測試、生產環境的網址,比如:http://localhost:4000、https://xxxxx.com
  • 「已獲授權的重定向 URI」填你項目的測試、生產環境的網址加上「/auth/google/callback」(實際上這個路徑取決於後面你項目代碼中聲明的路徑),比如:http://localhost:4000/auth/google/callback、https://xxxxx.com/auth/google/callback
  1. 同時單獨保存下該頁面上出現的「客户端 ID」和「客户端密鑰」(另外找安全的地方保存。一段時間後頁面上將不支持保存密鑰)
  2. 點擊底部的創建或保存按鈕。

複製粘貼一些模板代碼


接下來回到 Phoenix 項目中。

  1. 安裝unberauth
# 在 mix.exs 文件中追加
{:ueberauth_google, "~> 0.10.8"}

運行:mix deps.get 下載依賴

  1. 配置

首先是:config/dev.exs

config :ueberauth, Ueberauth,
  providers: [
    google: {Ueberauth.Strategy.Google, [default_scope: "email profile"]}
  ]

然後是:config/prod.exs

config :ueberauth, Ueberauth,
  providers: [
    google: {Ueberauth.Strategy.Google, [default_scope: "email profile", callback_scheme: "https"]}
  ]

上面這兩個的區別是 prod 環境追加了:callback_scheme,unberauth的默認值是 http,正好對應我開發環境是:http://localhost:4000,也對應前面在Google Dashboard的配置。這一點在 chriis 的文章中沒有提及,但是必要的,否則會導致 Google 登錄失敗。

最後是 config/runtime.exs

# 還記得前面讓你保存「客户端 ID」和「客户端密鑰」嗎?
# 到系統環境變量中設置,然後在這裏讀取到程序中
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
  client_id: System.get_env("GOOGLE_CLIENT_ID"),
  client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
  1. 調整用户數據表
記得將下面內容中的 XXXXX 替換為你的應用名

lib/dokuya/accounts/user.ex 文件中追加:

schema "users" do
  ...
  field :is_oauth_user, :boolean, default: false
  ...
end

# 你可以根據你的實際user情況,調整這裏的字段。
def oauth_registration_changeset(user, attrs, opts \\ []) do
  user
  |> cast(attrs, [:email])
  |> validate_required([:email])
  |> validate_email(opts)
  |> put_change(:is_oauth_user, true)
end

新增加了 schema 字段後,記得也創建一個新的數據庫遷移文件:mix ecto.gen.migration add_is_oauth_user_to_users

在新生成的 migration 文件中追加:

defmodule Dokuya.Repo.Migrations.AddOauthUser do
  use Ecto.Migration

  def change do
    alter table(:users) do
      add :is_oauth_user, :boolean, default: false
      # 因為 oauth 登錄不需要密碼,所以將 hashed_password 字段設置為可空
      modify :hashed_password, :string, null: true
    end
  end
end

然後運行遷移命令:mix ecto.migrate,修改數據庫結構。

lib/xxxxx/accounts.ex 文件中追加:

def register_oauth_user(attrs) do
  %User{}
  |> User.oauth_registration_changeset(attrs)
  |> Repo.insert()
end
  1. 追加核心 Controller 代碼

在該目錄創建一個文件:lib/xxxxxx_web/controllers/google_auth_controller.ex

文件內容如下:

defmodule XXXXWeb.GoogleAuthController do
  require Logger
  use XXXXXWeb, :controller
  plug Ueberauth

  alias XXXXX.Accounts
  alias XXXXXWeb.UserAuth

  def request(conn, _params) do
    Phoenix.Controller.redirect(conn, to: Ueberauth.Strategy.Helpers.callback_url(conn))
  end

  def callback(conn, params) do
    create(conn, params, "Welcome back!")
  end

  # google login
  defp create(%{assigns: %{ueberauth_auth: auth}} = conn, _params, info) do
    email = auth.info.email

    case Accounts.get_user_by_email(email) do
      nil ->
        # User does not exist, so create a new user
        # 因為我只需要 email,所以這裏只傳遞 email,其實 auth.info 中有更多用户相關的信息可以使用。
        case Accounts.register_oauth_user(%{
               email: email
             }) do
          {:ok, user} ->
            Logger.info("Google login success: #{inspect(user)}")

            conn
            |> put_flash(:info, info)
            |> UserAuth.log_in_user(user, %{"remember_me" => "true"})

          {:error, changeset} ->
            Logger.error("Failed to create user #{inspect(changeset)}.")

            conn
            |> put_flash(:error, "Failed to create user.")
            |> redirect(to: ~p"/")
        end

      user ->
        # User exists, update session or other details if necessary
        conn
        |> put_flash(:info, info)
        |> UserAuth.log_in_user(user, %{"remember_me" => "true"})
    end
  end
end
  1. 增加新的路由

lib/xxxx_web/router.ex 中追加

  scope "/auth", DokuyaWeb do
    # 記得追加這一步,否則登錄過程中會 fetch_session 相關的錯誤。
    pipe_through :browser

    get "/:provider", GoogleAuthController, :request
    get "/:provider/callback", GoogleAuthController, :callback
  end
  1. 最後一步,將登錄按鈕放到你的任何像放置的頁面上:
<.button href={~p"/auth/google"}>Login with Google</.button>

大功告成。現在用户可以通過 Google 登錄方便地訪問你的網站。

Add a new Comments

Some HTML is okay.