Setting up Oauth2 in Phoenix
First add the dependency to your mix.exs
# /mix.exs
defp deps do
[
...
{:ueberauth_google, "~> 0.10"}
]
end
Then configure Ueberauth within your application
# /config.exs
...
# Ueberauth Config
config :ueberauth, Ueberauth,
providers: [
google:
{Ueberauth.Strategy.Google,
[default_scope: "email profile openid"]
}
]
config :ueberauth, Ueberauth.Strategy.Google.OAuth,
client_id: <client_id> # generated by google
client_secret: <client_secret> # generated by google
redirect_uri: "https://myappsite/auth/google/callback"
The :request
route is what the app client will use.
The :callback
route is what google will use.
In this instance the callback route will look something like: https://myappsite/auth/google/callback
A short summary of this process looks like this:
A request is made with Ueberauth through your application, to Google. Ueberauth essentially generates a client secret, and to ensure integrity of the data returned, that secret is encrypted and sent along with the request to Google. Google then returns this secret along with user data if the request was successful. The secret is then decoded and if the decoded secret, and the one stored locally are identical, it's safe to assume the returned data is true.
This data is returned to your application through the :callback
route.
# /router.ex
# Authentication routes
scope "/auth", ExampleWeb do
pipe_through :browser
get "/:provider", OauthController, :request
get "/:provider/callback", OauthController, :callback
end
Next we need the controller that will handle the oauth requests.
defmodule MyAppWeb.OauthController do
@moduledoc """
Auth controller responsible for handling Ueberauth responses
"""
use MyAppWeb, :controller
plug Ueberauth
alias MyApp.Accounts.User
alias MyApp.Accounts
alias MyAppWeb.UserAuth
def callback(%{assigns: %{ueberauth_auth: auth}} = conn, _params) do
user_params = %{
first_name: auth.info.first_name,
last_name: auth.info.last_name,
email: auth.info.email,
avatar_url: auth.info.image,
locale: auth.extra.raw_info.user["locale"],
oauth_user_id: auth.extra.raw_info.user["sub"]
}
changeset = User.changeset(%User{}, user_params)
case Accounts.insert_or_update_oauth_user(changeset) do
{:ok, user} ->
conn
|> put_flash(:info, "You are now signed in")
|> UserAuth.log_in_user(user)
{:error, _reason} ->
conn
|> put_flash(:error, "Error signing in")
|> redirect(to: "/")
end
end
def callback(%{assigns: %{ueberauth_failure: _fails}} = conn, _params) do
conn
|> put_flash(:error, "Failed to authenticate")
|> redirect(to: "/")
end
end
The insert_or_update_oauth_user/1
function.
# /accounts.ex
defmodule MyApp.Accounts do
...
def insert_or_update_oauth_user(%Ecto.Changeset(changes: %{oauth_user_id: id} = changes}) do
case Repo.get_by(User, oauth_user_id: id) do
nil -> %User{oauth_user_id: id}
user -> user
end
|> User.changeset(changes)
|> Repo.insert_or_update()
end
...
end
You can see here we are using the unique user id provided by google to authenticate the user. This id is unique to a users' google account and no one will ever have the same one. This is secure because of the process we mentioned before.
Client secret generated, encrypted and sent along with the request to google over an HTTPS connection, user data is returned and validated locally.
To connect it to the front-end, you simply call the :request
path. Something like:
<a class="button" href={ Routes.oauth_path(@conn, :request, "google") }>
Sign in with Google
</a>