Recently I was wanting to add Google oauth account registration and login to my Phoenix LiveView app. I found a few articles, but there were a handful of errors in the tutorial. I adapted the changes to work with my project and figured I would write about it here to help anyone else looking to set up their app as well.
Assumptions
This guide assumes the following:
-
You’ve run
mix phx.gen.auth Accounts User users-
You haven’t made any major changes to the
Userstruct.
-
You haven’t made any major changes to the
-
You’ve set up an application in Google
-
The application has the following scopes in google granted
-
email -
profile -
The application has a redirect url safelisted for
http://localhost:4000/google/auth/callback
-
-
The application has the following scopes in google granted
-
You have set the following environment variables
-
GOOGLE_CLIENT_ID -
GOOGLE_CLIENT_SECRET
-
-
The top level module is
App, a lot of tutorials start withMyApp, just rename this to whatever the top level module is for your app.
Files
mix.exs
First add the deps to your mix.exs. Then run mix deps.get.
def deps do
[
# ...
{:ueberauth, "~> 0.10.8"},
{:ueberauth_google, "~> 0.12.1"}
]
end
config/config.exs
Update your config.exs file to have the following configuration.
config :ueberauth, Ueberauth,
providers: [
google: {Ueberauth.Strategy.Google, [default_scope: "email profile"]}
]
config/runtime.exs
Update your runtime.exs to pull the environment variables to configure the client_id and client_secret.
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET")
lib/app/accounts.ex
Add a function to the accounts module.
defmodule App.Accounts do
def register_oauth_user(attrs) do
%User{}
|> User.oauth_registration_changeset(attrs)
|> Repo.insert()
end
end
lib/app/accounts/user.ex
Update your user.ex schema to support the oauth flow.
defmodule App.Accounts.User do
schema "users" do
# ...
field :is_oauth_user, :boolean, default: false
# ...
end
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
end
lib/app_web/controllers/oauth_controller.ex
Add a controller which uses the new changeset and registration functions.
defmodule AppWeb.OAuthController do
alias AppWeb.UserAuth
alias App.Accounts
use AppWeb, :controller
require Logger
plug Ueberauth
def request(conn, _params) do
Phoenix.Controller.redirect(conn, to: Ueberauth.Strategy.Helpers.callback_url(conn))
end
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
email = auth.info.email
case Accounts.get_user_by_email(email) do
nil ->
user_params = %{email: email}
case Accounts.register_oauth_user(user_params) do
{:ok, user} ->
UserAuth.log_in_user(conn, user)
{:error, changeset} ->
Logger.error("Failed to create user #{inspect(changeset)}.")
conn
|> put_flash(:error, "Failed to create user.")
|> redirect(to: ~p"/")
end
user ->
UserAuth.log_in_user(conn, user)
end
end
end
lib/app_web/router.ex
Wire in the oAuth controller to support support the callback.
scope "/auth", AppWeb do
pipe_through :browser
get "/:provider", OAuthController, :request
get "/:provider/callback", OAuthController, :callback
end
priv/repo/migrations/[timestamp]_add_oauth_user.exs
Run mix ecto.gen.migration add_oauth_user. Then in the file that’s generated replace the contents with the following.
defmodule App.Repo.Migrations.AddOauthUser do
use Ecto.Migration
def up do
alter table(:users) do
add :is_oauth_user, :boolean, default: false
modify :hashed_password, :string, null: true
end
end
def down do
alter table(:users) do
remove :is_oauth_user
modify :hashed_password, :string, null: false
end
end
end
Testing
Finally run mix ecto.migrate. Once the migrations are completed, run iex -S mix phx.server, then visit https://localhost:4000/google/auth. From there you should be able to go through the full login flow and successfully create a user from the login.