This is an example application based off the OAuth2 Example Application showing how one can use 3-legged OAuth for Google using the Phoenix framework and the OAuth2 library.
Try the demo out at https://elixir-auth.appspot.com!
To start the application:
- Register a new application in the Google Developer Console
- Click
Create Credentials
>OAuth client ID
- Select "Web application" as the application type
- Enter http://localhost:4000/auth/callback as an authorized redirect URI
- Click
Dashboard
>ENABLE APIS AND SERVICES
and enable "Google+ API"
- Click
- Set the
GOOGLE_REDIRECT_URI
environment variable to the callback URL - Set the
GOOGLE_CLIENT_ID
andGOOGLE_CLIENT_SECRET
environment variables - Install Elixir dependencies with
mix deps.get
- Install NodeJS dependencies with
npm install
- Start the application with
mix phoenix.server
Now you can visit http://localhost:4000
from your browser and click "Sign in
with Google".
After authorizing the application, you should see the welcome message above.
The majority of the work is done in the Google OAuth model
(web/oauth/google.ex
) and the Auth Controller
(web/controllers/auth_controller.ex
).
The Google OAuth model wraps OAuth2 and configures the client crendentials, redirect URI, and OAuth2 token and authorization URLs.
defmodule Google do
use OAuth2.Strategy
# Public API
def client do
OAuth2.Client.new([
strategy: __MODULE__,
client_id: System.get_env("GOOGLE_CLIENT_ID"),
client_secret: System.get_env("GOOGLE_CLIENT_SECRET"),
redirect_uri: System.get_env("GOOGLE_REDIRECT_URI"),
site: "https://accounts.google.com",
authorize_url: "/o/oauth2/auth",
token_url: "/o/oauth2/token"
])
end
def authorize_url!(params \\ []) do
OAuth2.Client.authorize_url!(client(), params)
end
# you can pass options to the underlying http library via `opts` parameter
def get_token!(params \\ [], headers \\ [], opts \\ []) do
OAuth2.Client.get_token!(client(), params, headers, opts)
end
# Strategy Callbacks
def authorize_url(client, params) do
OAuth2.Strategy.AuthCode.authorize_url(client, params)
end
def get_token(client, params, headers) do
client
|> put_param(:client_secret, client.client_secret)
|> put_header("accept", "application/json")
|> OAuth2.Strategy.AuthCode.get_token(params, headers)
end
end
The Auth Controller handles redirecting the user to the authorize URL, exchanging an authorization code for an access token, and making a request to Google's API to retrieve a profile picture.
defmodule GoogleOAuth2Example.AuthController do
use GoogleOAuth2Example.Web, :controller
@doc """
This action is reached via `/auth/callback` is the the callback URL that
Google's OAuth2 provider will redirect the user back to with a `code` that will
be used to request an access token. The access token will then be used to
access protected resources on behalf of the user.
"""
def callback(conn, %{"code" => code}) do
# Exchange an auth code for an access token
client = Google.get_token!(code: code)
# Request the user's data with the access token
scope = "https://www.googleapis.com/plus/v1/people/me/openIdConnect"
%{body: user} = OAuth2.Client.get!(client, scope)
current_user = %{
name: user["name"],
avatar: String.replace_suffix(user["picture"], "?sz=50", "?sz=400")
}
# Store the user in the session under `:current_user` and redirect to /.
# In most cases, we'd probably just store the user's ID that can be used
# to fetch from the database. In this case, since this example app has no
# database, I'm just storing the user map.
#
# If you need to make additional resource requests, you may want to store
# the access token as well.
conn
|> put_session(:current_user, current_user)
|> put_session(:access_token, client.token.access_token)
|> redirect(to: "/")
end
@doc """
This action is reached via `/auth` and redirects to the Google OAuth2 provider.
"""
def index(conn, _params) do
redirect conn, external: Google.authorize_url!(
scope: "https://www.googleapis.com/auth/userinfo.email"
)
end
def delete(conn, _params) do
conn
|> put_flash(:info, "You have been logged out!")
|> configure_session(drop: true)
|> redirect(to: "/")
end
end