58
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Phoenix で OAuth 認証をする

Last updated at Posted at 2015-08-31

Phoenix で OAuth 認証をやってみます。
今回は GitHub の OAuth を使いますが、Twitter や Facebook といったものにもつかえると思います。

また、認証用ライブラリはコチラを利用します。実装はこちらに公開されているサンプルを追っていくカタチとなります。

Phoenix アプリケーションの作成についてはコチラをご参照ください。

下準備: GitHub アプリケーションを登録する

まずは GitHub にアプリケーションを登録して、各種キーを手に入れます。
GitHub にログインした状態で登録ページに行き、フォームを埋めていきます。
今回はテスト用のアプリケーションなので、基本的には適当に埋めていけば良いのですが、callback URL だけ以下のように設定します。

http://localhost:4000/auth/callback

スクリーンショット 2015-08-31 21.40.48.png

Register application をクリックすると、Client ID, Client Secret が表示されます。

Phoenix アプリケーションをセットアップする

新規でアプリケーションを作成します。

$ mix phoenix.new github_oauth

次に OAuth 認証のライブラリをインストールします。
mix.exs に以下の一行を追記します

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 というファイルを作成し、以下のように記述します。

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_idclient_secret は適宜書き換えてください。

認証用コントローラを作成する

/web/controllers/auth_controller.ex というファイルを作成し、以下のように記述します。

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 に実装します。

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 でエラーが発生するためです。

app.html.eex
...
    <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 を以下のように編集します。

index.html
<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

以上で実装は完了です。

ログインしてみる

以下のような画面遷移が出来れば成功です。

i) トップ画面(ログイン後)
スクリーンショット 2015-08-31 22.55.14.png

ii) GitHub 認証画面
スクリーンショット 2015-08-31 22.55.28.png

iii) パスワード入力画面
スクリーンショット 2015-08-31 22.56.00.png

iv) トップ画面(ログイン後)
スクリーンショット 2015-09-01 0.12.29.png

感想

  • OAuth 認証の流れが理解できていればすんなりと実装できる
  • パイプライン内で認証済みかどうかを判定してみてもいいかもしれない
    • 今の実装だとログイン後も /auth にアクセスすることでログイン処理が走ってしまう
    • 認証していなかったらログイン画面にリダイレクト、的なほうが好ましそう
  • ログアウトする(ユーザ情報をクリアする)処理を組んでもよかったかもしれない

追記(2016/01/11)

  • oauth2 ライブラリのバージョンが上がり一部仕様が変更されていましたので、内容を修正しました
  • @hykw さん、ご指摘いただきありがとうございました :bow:
58
59
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
58
59

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?