RailsでGoogle AppsのOpenID対応アプリを作る

RailsGoogle Apps for your domainにOpenID認証をするサンプルアプリを作りました。
以下、サンプル作成手順です。

ruby-openidruby-openid-apps-discoveryをインストール

>gem install ruby-openid
>gem install ruby-openid-apps-discovery

railsアプリを作ります

>rails gapps-openid

app/controllers/consumer_controller.rbを作成

require 'pathname'

require "openid"
require 'openid/extensions/ax'
require 'openid/store/filesystem'
require 'gapps_openid'

class ConsumerController < ApplicationController
  layout nil

  def index
    # render an openid form
  end

  def start
    begin
      domain = params[:domain]
      if domain.nil?
        flash[:error] = "Enter an Google Apps Domain"
        redirect_to :action => 'index'
        return
      end
      oidreq = consumer.begin(domain)
    rescue OpenID::OpenIDError => e
      flash[:error] = "Discovery failed for #{domain}: #{e}"
      redirect_to :action => 'index'
      return
    end
    
    ax = OpenID::AX::FetchRequest.new
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/contact/email', 'email', true))
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/first', 'firstname', true))
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/last', 'lastname', true))
    oidreq.add_extension(ax)
    
    return_to = url_for :action => 'complete', :only_path => false
    realm = url_for :action => 'index', :only_path => false
    
    if oidreq.send_redirect?(realm, return_to)
      redirect_to oidreq.redirect_url(realm, return_to)
    else
      render :text => oidreq.html_markup(realm, return_to, {'id' => 'openid_form'})
    end
  end

  def complete
    # FIXME - url_for some action is not necessarily the current URL.
    current_url = url_for(:action => 'complete', :only_path => false)
    parameters = params.reject{|k,v|request.path_parameters[k]}
    oidresp = consumer.complete(parameters, current_url)
    case oidresp.status
    when OpenID::Consumer::FAILURE
      if oidresp.display_identifier
        flash[:error] = ("Verification of #{oidresp.display_identifier}"\
                         " failed: #{oidresp.message}")
      else
        flash[:error] = "Verification failed: #{oidresp.message}"
      end
    when OpenID::Consumer::SUCCESS
      flash[:success] = ("Verification of #{oidresp.display_identifier}"\
                         " succeeded.")
      ax_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)
      ax_message = "Attribute Exchange data was requested"
      if ax_resp.data.empty?
        ax_message << ", but none was returned."
      else
        ax_message << ". The following data were send:"
        ax_resp.data.each {|k,v|
          ax_message << "<br/><b>#{k}</b>: #{v}"
        }
      end
      flash[:ax_results] = ax_message
    when OpenID::Consumer::SETUP_NEEDED
      flash[:alert] = "Immediate request failed - Setup Needed"
    when OpenID::Consumer::CANCEL
      flash[:alert] = "OpenID transaction cancelled."
    else
    end
    redirect_to :action => 'index'
  end

  private

  def consumer
    if @consumer.nil?
      dir = Pathname.new(RAILS_ROOT).join('db').join('cstore')
      store = OpenID::Store::Filesystem.new(dir)
      @consumer = OpenID::Consumer.new(session, store)
    end
    return @consumer
  end
end

app/view/consumer/index.html.erbを作成

<html>
<head>
<title>Rails OpenID Example for Google Apps</title>
</head>
  <style type="text/css">
      * {
        font-family: verdana,sans-serif;
      }
      body {
        width: 50em;
        margin: 1em;
      }
      div {
        padding: .5em;
      }
      .alert {
        border: 1px solid #e7dc2b;
        background: #fff888;
      }
      .error {
        border: 1px solid #ff0000;
        background: #ffaaaa;
      }
      .success {
        border: 1px solid #00ff00;
        background: #aaffaa;
      }
      #verify-form {
        border: 1px solid #777777;
        background: #dddddd;
        margin-top: 1em;
        padding-bottom: 0em;
      }
  </style>
  <body>
    <h1>Rails OpenID Example for Google Apps</h1>
    <% if flash[:alert] %>
      <div class='alert'>
       <%= h(flash[:alert]) %>
      </div>
    <% end %>
    <% if flash[:error] %>
      <div class='error'>
       <%= h(flash[:error]) %>
      </div>
    <% end %>
    <% if flash[:success] %>
      <div class='success'>
       <%= h(flash[:success]) %>
      </div>
    <% end %>
    <% if flash[:ax_results] %>
      <div class='alert'>
      <%= flash[:ax_results] %>
      </div>
    <% end %>
    <div id="verify-form">
      <form method="get" accept-charset="UTF-8" 
            action='<%= url_for :action => 'start' %>'>
        Domain:
        <input type="text" class="openid" name="domain" />
        <input type="submit" value="Verify" />
      </form>
    </div>
  </body>
</html>

environment.rbを修正

DBは使わないので、active_recordを使わないように設定

  config.frameworks -= [ :active_record ]

修正ポイント

このコードは以下の場所にあるruby-openidのサンプルを基にしています。
lib\ruby\gems\1.8\gems\ruby-openid-2.1.7\examples\rails_openid
そこからの修正ポイントは以下の通りです。

ドメインのみを入力すれば認証できるようにする
require 'gapps_openid'

って書きます。こう書くとドメインからOPのエンドポイントURLを探してきて、それで認証しようとしてくれます。
詳しくは⇒Discovering OpenID Endpoints for Hosted Domains - Google Federated Login API | Google Groups

Attribute Exchangeでログインユーザのアカウント情報をGoogleから取得

GoogleAttribute Exchangeを使って、ユーザの属性情報を渡してくれます。

AXで渡してって書きます。

    ax = OpenID::AX::FetchRequest.new
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/contact/email', 'email', true))
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/first', 'firstname', true))
    ax.add(OpenID::AX::AttrInfo.new('http://axschema.org/namePerson/last', 'lastname', true))
    oidreq.add_extension(ax)

返ってきたのを受け取ります。

      ax_resp = OpenID::AX::FetchResponse.from_success_response(oidresp)

動かしてみます

>ruby script\server

http://localhost:3000/にアクセス。

Google Appsドメインを入力します。

ログインします。

名前とメールアドレスの取得許可を聞かれます。

ログイン&アカウント情報取得できました。

終わり。次はOAuthとMarketplace。