Google OAuth 2.0 を使い、Web アプリケーションに認証機能を追加する

=== 追記 2017年6月9日 ===

この記事は Google OAuth を利用し、
自分のウェブアプリケーションに「OAuth 認証」させようという記事です。
OAuth 2.0 は本来、認証(Authentication)ではなく認可(Authorization)を行うための枠組みです。

そのあたりの知識が曖昧な時期に「OAuth 認証」のための記事を書いたため、
いま見ると古びていて、認識が間違えているところもあるかと思います。

OAuth 2.0 のきちんとした知識については、@TakahikoKawasaki 先生の記事を参考にしてください。

=== 追記終了 ===


ユーザー登録機能のある Webサービスを作るとき、
最初に考えるものとしてログイン機能、認証機能等がある。
これは色々と考慮するべきところが多く、
いい加減に作るとセキュリティの問題を抱えてしまう。

またそうしたユーザー登録機能は、ユーザーに対し、悪いユーザー体験を与えてしまう。
Web サービスを使ってみようと思ったとしても、最初にメールアドレスやパスワードの
登録を行わなければならず、面倒である。


別のアプローチとして、独自のユーザー登録フローを実行するのではなく、
Google、Twetter、Facebook などを介し、Webサービスにログインできるようにする仕組みもある。
OpenID などと呼ばれる)

例えば既存の Web サービスで「Trello」というものがある。
これをはじめるとき、次のような流れで Webサービスを使いはじめることができる。

この流れの中で、ユーザーが行う操作は「Google アカウントで認証する」をクリックし、
Google 側で同意ボタンを押すだけだ。

Webサービス独自のユーザー認証機能を実装するのではなく、
信頼性のある外部サービスに認証を委託して、
自分のサービス側では、ユーザー情報の管理のみを行う仕組みを、
「OAuth を使ったログイン認証」と呼ぶ。



Google アカウントを持っているならば、
その Webサービス の新規登録行わなくてもすぐに利用開始できるような機能を、
実装する方法について説明しようと思う。

今回は、次のような要件で実装を行った。

Ruby on Rails を実装例としたが、言語・フレームワークに依存している部分は少ないため、
他言語、他フレームワークにも簡単に展開可能だと思われる。



今回の実装にあたって、大部分を、下記の記事を参考に行った。

Using OAuth 2.0 for Login (OpenID Connect) - Google Accounts Authentication and Authorization

Google OAuth の認証方法をは使った記事で、日本語のチュートリアルはあまり数がなく、
あったとしても古い内容になっていたため、
こちらの記事にすることにした。

皆様のお役に立てれば幸いである。


今回掲載する内容が、古くなっているように感じたら、
この Google 本家の情報を参照していただきたい。




実装の流れ

Google OAuth 2.0 認証を使って、自分のWebサービスに認証機能を付加するには、
下記のような流れで作業を行う。

  1. Google アカウントを作成(作成済みならば不要)
  2. Google Developers Console から、プロジェクトを作成、認証情報や、OAuth 用クライアントID等を取得
  3. Web アプリケーションに、Google と OAuth 認証する流れを実装

Google アカウントはすでに所有していると仮定し、
ここからは Google Developers Console の設定を方法と、
Web アプリケーション側の実装を解説する。



Google Developers Console の設定について

Google Developers Console を開く。
Google Developers Console

ここで「プロジェクトを作成」を選択。
新しいプロジェクトを作成する。

作成したプロジェクトの中に入ると、画面左側にメニューがいくつか表示される。
今回の OAuth 認証で必要になるのは、主に下記の3項目である。

  • API
  • 認証情報
  • 同意画面



API

Google OAuth を使ったユーザー認証の場合、必要な API は特にない。
そのため API ページの項目は全て OFF にして問題ない。
下記のような感じになる。


認証情報

認証情報では、OAuth 認証に必要な Client ID の発行、
OAuth のリダイレクトURLを登録する必要がある。

「クライアントID」と「クライアントシークレット」は、
Webアプリケーション側で利用する値なので、メモしておく。
「リダイレクトURI」に設定する内容もとても重要である。
今回、ローカル環境、本番環境の、2つのURIを記述することにする。

「公開APIへのアクセス」の項目は今回は不要。


同意画面

Webサービスから Google へリダイレクトした時の
認証同意画面に表示する項目についての設定である。
「サービス名」は必須なので任意のものを入力していただきたい。

下記のような感じとなる。


ここまでの内容で、
Google Developers Console の設定は完了だ。




Web アプリケーション側の実装について

Using OAuth 2.0 for Login (OpenID Connect) - Google Accounts Authentication and Authorization

上記の記事を参考にする。

Web アプリケーション側の実装としては、
下記のような流れで Google との連携、ユーザー登録、認証まで進める。
(ここからWebアプリケーション側の処理を「クライアント」と表記し、
 Google 側の処理を「Google」と単純に表記する)

  1. 【クライアント】ユーザー登録、認証ボタンをクリックした時、Google に対して OAuth 認証をリクエス
  2. Google】認証同意画面を表示し、その結果がクライアントへリダイレクトされる
  3. 【クライアント】認証が OK ならば、Google に認証コードとクライアント情報を送信、トークンを取得
  4. 【クライアント】取得したトークンを使い、GoogleAPI へアクセス(メールアドレスを取得)


下記に Ruby on Rails で実装した認証処理の例を掲載する。

この辺りの Google との連携を簡単にするために 「Google API Client」の Ruby 版 が提供されている。多分、これを使いこなせれば、かなり楽だったんじゃないかなぁと思ったが、私にはちょっと使い方が難しすぎた...。結局、認証部分は自作してしまった。ご了承いただければと思う。

Google に対して OAuth 2.0 認証を要求する

たとえば Webアプリケーション側に下記のようなボタンを表示し、
そこをクリックすることで利用開始できるようにしたいとする。
下記のボタンをクリックした時に POST リクエストを送信する。

ここの実装については、詳細は下記の項を参照いただきたい。

2. Send an authentication request to Google

POST リクエストの実装は、具体的には下記のような処理となった。

  # POST /signin
  def signin
    # 認証用トークン (安全な乱数)
    state_token = SecureRandom.urlsafe_base64(32) # URLにのせて送信するため

    # トークンをセッションに保管しておく
    # 自動ログインのフォーム入力内容も、保管しておく
    session[:oauth_google] = {:state => state_token}

    # URL の組み立て
    url = "https://accounts.google.com/o/oauth2/auth?"\
      "client_id=#{Constants::GOOGLE_CLIENT_ID}&"\
      "response_type=code&"\
      "scope=email%20profile&"\
      "redirect_uri=http://localhost:3000/oauth-google&"\
      "state=#{state_token}"

    redirect_to url
  end

上記のアクションが終わると、Google の認証同意画面へと遷移する。

ここで同意すると「redirect_uri」で指定した URI にリダイレクトされる。
今回の例では「http://localhost:3000/oauth-google」へリダイレクトされる。


この URI に対するアクションの実装はこうだ。

  # GET /oauth-google
  # Google OAuth による認証後のコールバック呼び出し
  def oauth_google
    if session[:oauth_google] && session[:oauth_google][:state].present?
      # CSRF 対策
      if session[:oauth_google][:state] == params[:state]
        if params[:code].present?
          # code を元に token 認証する
          response = Net::HTTP::post_form(URI.parse("https://accounts.google.com/o/oauth2/token"), {
            :code => params[:code],
            :client_id => Constants::GOOGLE_CLIENT_ID,
            :client_secret => Constants::GOOGLE_CLIENT_SECRET,
            :redirect_uri => "http://localhost:3000/oauth-google",
            :grant_type => "authorization_code"
          })

          # token レスポンス
          result = JSON.parse(response.body)

          if result["expires_in"] > 0 && result["id_token"].present?
            api_response = Net::HTTP.get URI.parse("https://www.googleapis.com/oauth2/v1/tokeninfo?id_token=#{result['id_token']}")
            api_result = JSON.parse(api_response)

            if api_result["issuer"] == "accounts.google.com" && api_result["issued_to"] == Constants::GOOGLE_CLIENT_ID && api_result["audience"] == Constants::GOOGLE_CLIENT_ID && api_result["audience"] == Constants::GOOGLE_CLIENT_ID && api_result["verified_email"]
              email = api_result["email"]

              puts "Signin: success Google OAuth '#{email}'"

              ActiveRecord::Base.transaction do
                # 既存のユーザーかどうか調べる
                user = User.where("email = ?", email).first

                if user.nil?
                  # 新規ユーザー登録(users テーブルというものがあると仮定)
                  user = User.create({:email => email})

                  puts "Signin: new user '#{email}'"
                end

                # セッションに認証情報を付与
                session[:user_id] = user.id

              end# ActiveRecord::Base.transaction

              redirect_to root_path
              return
            end
          end
        end
      end
    end

    flash.now.alert = "何らかの理由で認証に失敗しました"
    render :template => "index"
  end

この辺りについて詳しくは、下記の項を参考にしている。

4. Exchange code for access token and ID token



流れとしては、リダイレクト時に取得した認証コードを使って、
Google に対して、トークン取得のリクエストを送信する。
無事にトークンが取れたら、その認証トークンを使って、任意のAPIを呼び出す。
という流れである。


OAuth による認証に最後まで成功した場合は、この session に認証情報を付加する。
まだユーザーが作られていない状態ならば、
新規ユーザー登録まで、同時に行う。


おまけ:アカウント権限の画面について

Google の OAuth による認証機能を使うと、
認証を行ったユーザーの「アカウント権限」の項目に情報が増える。

アカウント権限

上記のページを閲覧すると、
今回実装したアプリケーションの認証許可の情報が増えている。
これは、利用しているユーザー側が、
任意のタイミングで認証を外したりすることもできる。

最後に

以上が Google の OAuth 2.0 認証を使って、
Webアプリケーションに認証機能を追加する流れと、実装例である。

実装としては Ruby on Rails を使っているが、
Google Client API に依存していないし、基本的には HTTPリクエストのみで完結している。

実装例の内容は OAuth 認証部分に特化するため、
実際のアプリケーションよりも処理を削って掲載しているので、
その辺りはご了承いただきたい。

また、根本的な誤りなどありましたら、
訂正いたしますので、コメント欄などでご指南頂けますと大変嬉しいです。

Enjoy Hacking