sifue's blog

プログラマな二児の父の日常 ポートフォリオは www.soichiro.org

Heroku + Sinatra + OAuth2.0 + Google API Client で Googleアカウントでログイン

前回のTwitterアカウントでログインに引き続き、今回は、Googleのアカウントを使ってGoogleのサービスにログイン済みの場合にはIDとPasswordを入力させることなくログインをさせる、OAuth2.0を使ったGoogleアカウントでログインの実装例の紹介です。
今回もまたHeroku + Sinatraを使っています。Google APIのOAuth2.0の実装はまだexperimentalの段階ですが、今後主流となっていくと思いますので認証するサンプルを作りました。GooleのOAuthのAPITwitterのように、読みと書きの2つしか用意されてないとうことはなく、細かくスコープが分けられていて非常に良い認証、権限の仕組みかなと思います。


実際に動くサンプルはこちら

http://google-login.heroku.com/
です。


基本的にリソースは、
http://code.google.com/intl/ja/apis/accounts/docs/OAuth2.html
を参考にしています。動きを見てみてOAuth2.0とはなんぞやと思う人はしっかり読んでみてください。


やり方は、結構簡単

  • APIs Console

https://code.google.com/apis/console
にアクセスして、自分の好きなGoogleアカウントでGoogleAPI Accessをさせるアプリケーションを作成します。Create OAuth2.0 API ProjectでWebserver Apllicationを登録します。


そして、

  • Client ID:
  • Client secret:

を取得。コールバックのURLはドメイン名があっていなくてはいけないので、ローカルでsinatraを動かしたりして検証するのであれば、localhost:4567のドメインのClient IDとClient secretを用意しましょう。もし、ログイン情報の他、他のスコープのAPIも利用するのであれば、All Servicesのところから使うAPIを設定することができます。


あとは以下のソースの、oauth_client_idとoauth_client_secretを書き換えるだけで利用できます。ちなみに利用しているGoogle API clientのgemは本来は、公開されているAPIを探してきて、それを利用するためのツールでもあります。ですが、ここではOAuth2.0のトークンとユーザー情報とメールアドレスのスコープを設定して情報を取得するためだけに利用しています。


ソースコードは以下の通り。GitHubからもダウンロード可能。

google_login.rb

require 'rubygems'
require 'sinatra'
require 'google/api_client'
require 'httpadapter/adapters/net_http'
require 'pp'
require 'yaml'

use Rack::Session::Pool, :expire_after => 86400 # 1 day

# Configuration
# See README for getting API id and secret

if (ARGV.size < 2)
  set :oauth_client_id, 'oauth_client_id'
  set :oauth_client_secret, 'oauth_client_secret'

  if (settings.oauth_client_id == 'oauth_client_id')
    puts 'See README for getting API id and secret.  Server terminated.'
    exit(0)
  end
else
  set :oauth_client_id, ARGV[0]
  set :oauth_client_secret, ARGV[1]
end

# Configuration that you probably don't have to change
set :oauth_scopes, 'https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email'

class TokenPair
  @refresh_token
  @access_token
  @expires_in
  @issued_at

  def update_token!(object)
    @refresh_token = object.refresh_token
    @access_token = object.access_token
    @expires_in = object.expires_in
    @issued_at = object.issued_at
  end

  def to_hash
    return {
      :refresh_token => @refresh_token,
      :access_token => @access_token,
      :expires_in => @expires_in,
      :issued_at => Time.at(@issued_at)
    }
  end
end

# At the beginning of any request, make sure the OAuth token is available.
# If it's not available, kick off the OAuth 2 flow to authorize.
before do
  @client = Google::APIClient.new(
    :authorization => :oauth_2,
    :host => 'www.googleapis.com',
    :http_adapter => HTTPAdapter::NetHTTPAdapter.new
  )

  @client.authorization.client_id = settings.oauth_client_id
  @client.authorization.client_secret = settings.oauth_client_secret
  @client.authorization.scope = settings.oauth_scopes
  @client.authorization.redirect_uri = to('/oauth2callback')
  @client.authorization.code = params[:code] if params[:code]
  if session[:token]
    # Load the access token here if it's available
    @client.authorization.update_token!(session[:token].to_hash)
  end

  # @service = @client.discovered_api('userinfo', 'v1')
  unless @client.authorization.access_token || request.path_info =~ /^\/oauth2/
    redirect to('/oauth2authorize')
  end
end

# Part of the OAuth flow
get '/oauth2authorize' do
  <<OUT
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Google Ruby API Login Sample</title>
</head>
<body>
<header><h1>Google Ruby API Login Sample(profile and mailadress)</h1></header>
<div class="box">
<a class='login' href='#{@client.authorization.authorization_uri.to_s}'>Login and get your profile!</a>
</div>
</body>
</html>
OUT
end

# Part of the OAuth flow
get '/oauth2callback' do
  @client.authorization.fetch_access_token!
  unless session[:token]
    token_pair = TokenPair.new
    token_pair.update_token!(@client.authorization)
    # Persist the token here
    session[:token] = token_pair
  end
  redirect to('/')
end

# The method you're probably actually interested in. This one lists a page of your
# most recent activities
get '/' do
  result = @client.execute(:uri => 'https://www.googleapis.com/oauth2/v1/userinfo')
  response = result.response
  json = response.to_s
  ary = YAML.load(json)
  profile_json = ary[2][0]
  profile = YAML.load(profile_json)
  <<OUT
<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <title>Google Ruby API Login Sample</title>
</head>
<body>
<header><h1>Google Ruby API Login Sample(profile and mailadress)</h1></header>
<div class="box">
<a class='logout' href='/logout'>Logout</a>
<h5> id : #{profile['id']} </h5>
<h5> email : #{profile['email']} </h5>
<h5> verified_email : #{profile['verified_email']} </h5>
<h5> name : #{profile['name']} </h5>
<h5> given_name : #{profile['given_name']} </h5>
<h5> family_name : #{profile['family_name']} </h5>
<h5> picture : #{profile['picture']} </h5>
<h5> gender : #{profile['gender']} </h5>
<h5> locale : #{profile['locale']} </h5>
</div>
</body>
</html>
OUT
end

get '/logout' do
  session[:token] = nil
  redirect to('/')
end


ローカルで動かすだけなら必要なgemを用意して、以上のファイルだけで

ruby google_login.rb 

でサーバーを起動して、http://localhost:4567にアクセスして動かすことができます。
流れとしてはTwitterの時と同じですが、ルートにアクセスの後、セッションにトークンが残っているか見て、あればクライアント取得、無ければアクセストークンの取得をさせるURLを表示。リンクを押してもらって取得、認証の後は、ユーザー情報で取得できるプロフィールとメールアドレスをページに表示します。ログアウトはセッション情報を空にするだけです。


一応、Herokuにpushする時に必要となる以下のconfig.ruとGemfileも用意しておきました。Gemfileは、ローカル環境でgem install bundlerがしてあれば、bundle installコマンドで必要なgemライブラリのインストールもしてくれます。便利。


config.ru

require './google_login'
run Sinatra::Application


Gemfile

# Detail http://devcenter.heroku.com/articles/bundler
source 'http://rubygems.org'
gem 'sinatra' , '1.3.1'
gem 'google-api-client' , '0.3.0'


基本的には以上3ファイルを同一フォルダに置いて、ローカルのgitリポジトリにcommitした後。

heroku create
git push heroku master


でHeroku上でも利用可能になると思います。その際にGoogleのClient IDのコールバックURLのドメインをHerokuのものに変更しておくことを忘れないようにしておいてください。


ちなみにRubyGoogle API ClientのOAuth2.0で利用できるAPIの数々が、

http://code.google.com/p/google-api-ruby-client/wiki/SupportedAPIs
にまとめられています。最近の流行りはやっぱりGoogle+ APIを使ったソーシャルアプリ開発なんでしょうかね。あまり自分はGoogle+活用できていませんが、チャンスがあればAPI使ってみたいです。ただ、制限がCourtesy limit: 1,000 queries/dayとなっていて、いろいろやるにはGoogleさんに相談してお金を払う必要がありそうですが...。悩ましい。


今回使ったユーザー情報取得APIの情報はこちら

  • Using OAuth 2.0 for Login (Experimental)

http://code.google.com/intl/ja/apis/accounts/docs/OAuth2Login.html


あと、なにげに自分のGoogleアカウントで認証したアプリケーションのアクセス許可の取り消しですが、Googleにログインしたときにでる、右上の自分の名前のところから、 アカウント設定 > アカウントの概要 > アプリケーションとサイトを認証する > 編集 から選択することができます。実にわかりにくいです...。知らずに認証してしまったアプリ等があったら、認証を取り消しておくのが無難です。


何か、指摘等あればコメントで教えてもらえると助かります。