Let’s create new Phoenix application:
mix phoenix.new app
Dependencies
We are going to use oauth2 library in our application. First we need to update our dependencies in mix.exs file:
# mix.exs
defmodule App.Mixfile do
# ...
def application do
[mod: {App, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger,
:phoenix_ecto, :postgrex, :oauth2]]
end
# ...
defp deps do
[{:phoenix, "~> 1.0.2"},
{:phoenix_ecto, "~> 1.1"},
{:postgrex, ">= 0.0.0"},
{:phoenix_html, "~> 2.1"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:cowboy, "~> 1.0"},
{:oauth2, "~> 0.3"}
]
end
end
Then we update dependencies with:
mix deps.get
Routes
Now we are good with dependencies, so it’s time to add routes:
# web/router.ex
defmodule App.Router do
# ...
scope "/auth", App do
pipe_through :browser # Use the default browser stack
get "/:provider", AuthController, :index
get "/:provider/callback", AuthController, :callback
end
# ...
end
AuthController
Next thing is to implement AuthController
# web/controllers/auth_controller.ex
defmodule App.AuthController do
use App.Web, :controller
def index(conn, %{"provider" => provider}) do
redirect conn, external: authorize_url!(provider)
end
def callback(conn, %{"provider" => provider, "code" => code}) do
token = get_token!(provider, code)
user = get_user!(provider, token)
conn
|> put_session(:current_user, user)
|> put_session(:access_token, token.access_token)
|> redirect(to: "/")
end
defp authorize_url!("google") do
Google.authorize_url!(scope: "email profile")
end
defp authorize_url!(_) do
raise "No matching provider available"
end
defp get_token!("google", code) do
Google.get_token!(code: code)
end
defp get_token!(_, _) do
raise "No matching provider available"
end
defp get_user!("google", token) do
user_url = "https://www.googleapis.com/plus/v1/people/me/openIdConnect"
OAuth2.AccessToken.get!(token, user_url)
end
end
Authentication strategy
Last thing to implement is Google module
# web/oauth/google.ex
defmodule Google do
use OAuth2.Strategy
alias OAuth2.Strategy.AuthCode
def client do
OAuth2.Client.new([
strategy: __MODULE__,
client_id: System.get_env("CLIENT_ID"),
client_secret: System.get_env("CLIENT_SECRET"),
redirect_uri: System.get_env("REDIRECT_URI"),
site: "https://accounts.google.com",
authorize_url: "https://accounts.google.com/o/oauth2/auth",
token_url: "https://accounts.google.com/o/oauth2/token"
])
end
def authorize_url!(params \\ []) do
OAuth2.Client.authorize_url!(client(), params)
end
def get_token!(params \\ [], headers \\ []) do
OAuth2.Client.get_token!(client(), params, headers)
end
# strategy callbacks
def authorize_url(client, params) do
AuthCode.authorize_url(client, params)
end
def get_token(client, params, headers) do
client
|> put_header("Accept", "application/json")
|> AuthCode.get_token(params, headers)
end
end
Basically we finished implementing authentication but we see no difference in our application. At least let’s add button and display information after we are logged.
Let’s start from replacing content from web/templates/page/index.html.eex with:
<!-- web/templates/page/index.html.eex -->
<%= if @current_user do %>
<h2>Welcome, <%= @current_user["name"] %>!</h2>
<% else %>
<%= link to: auth_path(@conn, :index, "google"), class: "btn btn-primary" do %>
Sign in with Google
<% end %>
<% end %>
Last changes we introduce in PageController
# web/controllers/page_controller.ex
defmodule App.PageController do
use App.Web, :controller
def index(conn, _params) do
conn
|> assign(:current_user, get_session(conn, :current_user))
|> render "index.html"
end
end
Result
Before authentication we should see:
and after:
Resources
In implementing Google OAuth 2.0 authentication I found helpful source code from repositories: