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
User
struct.
-
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.