Phoenix で OAuth 認証をやってみます。
今回は GitHub の OAuth を使いますが、Twitter や Facebook といったものにもつかえると思います。
また、認証用ライブラリはコチラを利用します。実装はこちらに公開されているサンプルを追っていくカタチとなります。
Phoenix アプリケーションの作成についてはコチラをご参照ください。
下準備: GitHub アプリケーションを登録する
まずは GitHub にアプリケーションを登録して、各種キーを手に入れます。
GitHub にログインした状態で登録ページに行き、フォームを埋めていきます。
今回はテスト用のアプリケーションなので、基本的には適当に埋めていけば良いのですが、callback URL だけ以下のように設定します。
http://localhost:4000/auth/callback
Register application をクリックすると、Client ID, Client Secret が表示されます。
Phoenix アプリケーションをセットアップする
新規でアプリケーションを作成します。
$ mix phoenix.new github_oauth
次に OAuth 認証のライブラリをインストールします。
mix.exs
に以下の一行を追記します
...
def application do
[mod: {GithubOauth, []},
applications: [:phoenix, :phoenix_html, :cowboy, :logger,
:phoenix_ecto, :postgrex,
# 以下を追記
:oauth2]]
end
...
{:phoenix_html, "~> 2.1"},
{:phoenix_live_reload, "~> 1.0", only: :dev},
{:cowboy, "~> 1.0"},
# 以下を追記
{:oauth2, "~> 0.3"}]
end
end
追記したライブラリをダウンロードしてセットアップ完了です。
$ mix deps.get
GitHub 認証ストラテジを追加する
/web/auth/github.ex
というファイルを作成し、以下のように記述します。
defmodule GitHub do
use OAuth2.Strategy
def new do
OAuth2.Client.new([
strategy: __MODULE__,
# 生成したクライアントID
client_id: "XXXXXXXXXXXXXXXXXXXX",
# 生成したクライアントシークレット
client_secret: "YYYYYYYYYYYYYYYYYYYY",
# 設定したコールバックURL
redirect_uri: "http://localhost:4000/auth/callback",
# GitHub API のサイト
site: "https://api.github.com",
# GitHub が提供する認証用 URL
authorize_url: "https://github.com/login/oauth/authorize",
# Github が提供するトークン取得用 URL
token_url: "https://github.com/login/oauth/access_token"
])
end
def authorize_url!(params \\ []) do
new()
|> put_param(:scope, "user") # スコープはひとまず user のみ
|> OAuth2.Client.authorize_url!(params)
end
def get_token!(params \\ [], headers \\ [], options \\ []) do
OAuth2.Client.get_token!(new(), params, headers, options)
end
def authorize_url(client, params) do
OAuth2.Strategy.AuthCode.authorize_url(client, params)
end
def get_token(client, params, headers) do
client
|> put_header("Accept", "application/json")
|> OAuth2.Strategy.AuthCode.get_token(params, headers)
end
end
client_id
と client_secret
は適宜書き換えてください。
認証用コントローラを作成する
/web/controllers/auth_controller.ex
というファイルを作成し、以下のように記述します。
defmodule GithubOauth.AuthController do
use GithubOauth.Web, :controller
plug :action
def index(conn, _params) do
# GitHub の認証ページへリダイレクトさせます
redirect conn, external: GitHub.authorize_url!
end
def callback(conn, %{"code" => code}) do
# 返却されたコードからトークンを取得します
token = GitHub.get_token!(code: code)
# アクセストークンを使ってユーザ情報取得 API にリクエストします
%{body: user} = OAuth2.AccessToken.get!(token, "/user")
# ユーザ情報をセッションへ詰めた後、ルートページへリダイレクトさせます
conn
|> put_session(:current_user, user)
|> put_session(:access_token, token.access_token)
|> redirect(to: "/")
end
end
こちらのコントローラでは、先ほど作成した GitHub ストラテジをもとに認証〜ユーザ情報取得を行っています。
基本的な OAuth 認証の流れですね。
ルーティングを設定する
今回のアプリケーションのルーティングは以下のようになります。
-
/
- ログイン済み(ユーザ情報アリ)の場合
- ユーザ情報を表示する
- それ以外の場合
- 認証を促す(/auth へ遷移する)ボタンを表示する
- ログイン済み(ユーザ情報アリ)の場合
-
/auth
- GitHub の認証ページをリダイレクトさせます
-
/auth/callback
- GitHub からのコールバックを処理します
- 最終的に / へリダイレクトされます
これを router.ex
に実装します。
defmodule GithubOauth.Router do
use GithubOauth.Web, :router
pipeline :browser do
plug :accepts, ["html"]
plug :fetch_session
# ユーザ情報を読み込む処理をバイプラインに追加します
plug :assign_current_user
end
scope "/", GithubOauth do
pipe_through :browser
get "/", PageController, :index
end
scope "/auth", GithubOauth do
pipe_through :browser
get "/", AuthController, :index
get "/callback", AuthController, :callback
end
# ユーザ情報をセッション情報から読み込みます
defp assign_current_user(conn, _) do
assign(conn, :current_user, get_session(conn, :current_user))
end
end
パイプラインに assign_current_user
を追加している点に注目です。これにより、このパイプラインを through する全てのリクエストで、ログインしているユーザ情報にアクセスできるようになります。
ページを作る
まず、web/templates/layout/app.html.eex
から以下の2行を削除します。fetch_flash
プラグの記載を省いたため flash storage が利用できず、get_flash/2
でエラーが発生するためです。
...
<div class="container" role="main">
<div class="header">
<!-- ココカラ -->
<ul class="nav nav-pills pull-right">
<li><a href="http://www.phoenixframework.org/docs">Get Started</a></li>
</ul>
<!-- ココマデを削除(なんとなく不恰好なので) -->
<span class="logo"></span>
</div>
<!-- ココカラ -->
<p class="alert alert-info" role="alert"><%= get_flash(@conn, :info) %></p>
<p class="alert alert-danger" role="alert"><%= get_flash(@conn, :error) %></p>
<!-- ココマデを削除 -->
<%= @inner %>
</div> <!-- /container -->
...
コメントアウトだけだとエラーになるので、しっかりと削除してください。
次に web/templates/page/index.html.eex
を以下のように編集します。
<div class="row">
<div class="col-xs-12">
<%= if @current_user do %>
<h2>Hello, <%= @current_user["name"] %>!</h2>
<img src="<%= @current_user["avatar_url"] %>" class="img-circle"/>
<% else %>
<a class="btn btn-primary btn-lg" href="<%= auth_path @conn, :index %>">
Sign in with GitHub
</a>
<% end %>
</div>
</div>
ユーザ情報がある場合は名前とアバター画像を、ない場合はログインボタンを表示します。
ちなみに、href に指定している auth_path @conn, :index
は、ルーティングで設定した auth スコープの :index に相当する URL を取得する関数です。
auth_path なんていつ設定したっけ...という感じですが、これは Phoenix が自動で生成してくれているようです。
現在設定されているルーティングの一覧は、以下のコマンドで確認できます。
$ mix phoenix.routes
page_path GET / GithubOauth.PageController :index
auth_path GET /auth GithubOauth.AuthController :index
auth_path GET /auth/callback GithubOauth.AuthController :callback
以上で実装は完了です。
ログインしてみる
以下のような画面遷移が出来れば成功です。
感想
- OAuth 認証の流れが理解できていればすんなりと実装できる
- パイプライン内で認証済みかどうかを判定してみてもいいかもしれない
- 今の実装だとログイン後も /auth にアクセスすることでログイン処理が走ってしまう
- 認証していなかったらログイン画面にリダイレクト、的なほうが好ましそう
- ログアウトする(ユーザ情報をクリアする)処理を組んでもよかったかもしれない
追記(2016/01/11)
- oauth2 ライブラリのバージョンが上がり一部仕様が変更されていましたので、内容を修正しました
- @hykw さん、ご指摘いただきありがとうございました