いますぐ使えるOpenID

第4回Railsで作るOpenID対応アプリケーション実践(前編)

はじめに

今回はいよいよ、アプリケーションを作りながら、OpenIDの実践的な使い方を解説します。サンプルとして作成するアプリケーションは、ミニブログと呼ばれるつぶやきブログです。いわゆるTwitterクローンです。利用者はミニブログにログインして、一言つぶやきます。ブログの一種ですので、当然ユーザ認証が必要になります。普通はパスワードを用いた認証が一般的ですが、今回はOpenIDを用いてユーザを認証するようにアプリケーションを作成していきます。

さて、認証と簡単に言いましたが、認証に必要となる機能をもう少し具体的に列挙してみましょう。

ユーザ登録
サービスを利用するために必要な情報(IDやメールアドレスなど)を利用者に入力してもらい、データベースへ登録する。
ログイン(認証)
利用者が入力した認証情報(パスワードなど)を元に利用者を認証し、ログインの可否を判定する。ログインに成功した利用者には、認証済みであることを識別するための識別子(セッションCookie)を発行する。
セッション管理
利用者から送信されるセッションCookieを元に、その利用者が認証済みであることを確認する。一定時間アクセスがない利用者はセッションを無効にする。
アクセス制御(認可)
利用者がアクセス先のリソースへの権限を持っているかどうかを判定する。
ログアウト
セッションを破棄し利用者の認証状態を解除する。
アカウント管理
利用者が登録した情報を変更できるようにする。パスワードの変更やパスワード紛失時の再発行も担当する。
図1 認証に必要となる機能
図1 認証に必要となる機能

いかがでしょうか。想像したよりも認証に必要となる機能は多岐に渡ります。OpenIDを使うことで、これらの機能がどのように変わるのか(または変わらないのか)を見ていきましょう。

なお、常にすべての機能が必要という訳ではなく、アプリケーションによって必要な機能は変わります。例えば、ブログのコメント欄に認証機能を組み込むのあれば、コメント投稿時だけ利用者を確認できればよいため、ユーザ登録やセッション管理は不要になります。何のために利用者を認証するのかは、事前にしっかりと考えておくことが大切でしょう。

RailsのOpenIDプラグイン(OpenID Authentication)

RailsにはOpenIDを用いてユーザを認証するためのOpenID Authenticationというプラグインが存在します。このプラグインはこの連載で紹介したruby-openidライブラリのラッパーです。OpenID Authenticationプラグインを使うことで、ruby-openidライブラリをそのまま使うよりも簡単に、OpenIDをRailsアプリケーションに組み込むことができます。RailsのOpenID Authenticationプラグインは、長い間OpenID 2.0に対応していませんでした。しかし、Josh Peekが提案したパッチが2008年3月27日にプラグインに取り込まれ、今では標準でOpenID 2.0に対応しています。

Railsにはユーザ認証のためのプラグインがいくつか用意されています。Rails 1.x系の頃にはacts_as_authenticatedプラグインがよく使われていましたが、Rails 2.xではRestful Authenticationプラグインが使われているようです。OpenID Authenticationプラグインはこれらの従来の認証プラグインと競合するものではありません。むしろ、これらのプラグインと併用して使うものといえるでしょう。それぞれのプラグインの機能を、表1で比較します。

表1 認証プラグインの機能比較
 Restful AuthenticationOpenID Authentication
ユーザ登録×
ログイン(認証)○(パスワード)○(OpenID)
セッション管理×
アクセス制御×
ログアウト×
アカウント管理××

とはいえ、まずはOpenID Authenticationプラグインを単体で利用します。Restful AuthenticationプラグインとOpenID Authenticationプラグインの連携は次回挑戦します。

OpenID Authenticationプラグインのインストールと初期設定

OpenID Authenticationプラグインは、以下のコマンドでインストールできます。

./script/plugin install open_id_authentication

インストールしたプラグインは、vender/plugin/open_id_authenticationに展開されます。展開先のREADMEに使い方やサンプルが書いてあるので、一読しておくとよいでしょう。OpenID Authenticationプラグインをインストールすると、以下のrakeタスクが使用できるようになります。

$ rake open_id_authentication:db:create

このタスクを実行すると、ruby-openidライブラリが使用するデータを保存するためのデータベースを生成します。生成されるテーブルは表2の通りです。

表2
テーブル名説明
open_id_authentication_associationsOPと共有する共通鍵を保存する
open_id_authentication_nonces認証ごとに使用するナンス(nonce)を保存する

OpenIDは、OP(OpenID Provider)とRP(Relying Party)の間で共通鍵を共有します。利用者がログインするたびに共通鍵を共有すると大変なので、共有した共通鍵はデータベースに保存しておき、二回目からは保存した鍵を使用します。また、ナンス(nonce)とは、利用者がOpenIDでログインしようとするたびに、OPからのOpenIDレスポンスに付与されるランダムな文字列です。同じナンスを一度しか受け入れないことで、利用者や悪意ある第三者がOpenIDレスポンスを再利用すること(リプレイ攻撃)を防ぎます。

モデル・コントローラの一覧

サンプルアプリケーションで作成したモデルの一覧を以下の表3に示します。特にOpenID認証で特徴的なのは、ユーザを管理するUserモデルの属性にパスワードがなく、代わりにOpenIDのアカウント名(Claimed Identifier)を保存するためのidentity_url属性が存在することです。

表3 モデルの一覧
モデル名属性値役割
Userid, nickname, identity_urlユーザ情報を管理する
Relationuser_id, follow_idユーザ同士のお気に入り関係を管理する
Messageuser_id, bodyユーザが投稿したつぶやきを管理する

次に、作成したコントローラの一覧を以下の表4に示します。コントローラの構成は、パスワード認証の場合とそれほど違いはありません。これは、前ページで解説したように、認証方式の違いはユーザ管理機能の一部に過ぎないためです。特にOpenID認証で特徴的なのは、ログイン処理を担当するSessionsControllerのcreateアクションです。

表4 コントローラの一覧
コントローラ名アクション(メソッド)役割
ApplicationControllerlogin_requiredユーザが認証済みかどうかを確認する
UsersControllernew, create, homeユーザ登録およびユーザのホーム画面
SessionsControllernew, create, destroyユーザのログイン、ログアウトを管理する
MessagesControllertimeline, index, show, create, destroyユーザのつぶやきを管理する

文字だけでは流れがつかみづらいので、画面遷移図を元にして認証の流れをご説明します。パスワード認証の場合と異なるのは、ユーザ登録の時も事前にOpenIDで利用者を認証することです。

図2 サンプルアプリケーションの画面遷移図
図2 サンプルアプリケーションの画面遷移図
  • (1)利用者がOpenIDアカウントを入力しログインボタンをクリックします。
  • (2)OpenIDアカウントを元にOPを発見し、OpenIDリクエストを生成して利用者をOPへ誘導します。
  • (3)OPは利用者を認証し、OpenIDレスポンスを生成して利用者をRPへ誘導します。
  • (4-A)認証成功し、かつユーザがすでに登録されていれば、ログイン済みと判定して利用者をサービス画面へ誘導します。
  • (4-B)認証成功し、かつユーザが未登録であれば、利用者をユーザ登録画面へ誘導します。
  • (4-C)認証に失敗すると、エラーメッセージをログイン画面に表示します。
  • (5),(6)ニックネームを入力しユーザ情報を登録します。

(1)から(4)までがログイン処理で、Sessionsコントローラが担当します。⁠5)から(6)までがユーザ登録処理で、Usersコントローラが担当します。では、それぞれの処理について見ていきましょう。

OpenID Authenticationプラグインによるユーザ認証

ログイン処理

OpenID Authenticationプラグインを使ったログインの流れを見ていきます。まずはログイン画面からです。ここはOpenIDアカウント(User-Supplied Identifier)を入力するフォームが存在するだけです。

view/sessions/new.html.erb
<% form_tag(session_url) do %>
  <label for="openid_url">OpenIDアカウント名を入力してください:</label>
  <%= text_field_tag "openid_url" %>
  <%= submit_tag 'Sign in', :disable_with => "Signing in…" %>
<% end %>

OpenIDの仕様では、アカウントを入力するフィールドに「openid_url」という名前を付けることが推奨されています。この名前を付けることで、Webブラウザのオートコンプリート機能によって他のサイトで入力した値を自動補完できるためです。フォームに入力したアカウント名はSessionsコントローラのcreateアクションへと送られます。createアクションのソースコードを以下に示します。

controller/sessions_controller.rb
  def create
    # OpenID でユーザを認証する (begin, completeの両方に対応)
    authenticate_with_open_id do |result, identity_url|
      if result.successful?
        if @current_user = User.find_by_identity_url(identity_url)
          # 認証成功でユーザが登録済みの場合はログイン成功 (A)
          successful_login
        else
          # 認証成功でユーザが未登録の場合はユーザ登録画面へ遷移 (B)
          session[:identity_url] = identity_url
          redirect_to(new_user_path)
        end
      else
        # 認証失敗の場合はエラーメッセージを表示 (C)
        failed_login result.message
      end
    end
  end

authenticate_with_open_idは、OpenID Authenticationプラグインが提供するメソッドです。連載の第2回で紹介したruby-openid付属のサンプルでは、OPへOpenIDリクエストを送信するbeginメソッドと、OPからのOpenIDレスポンスを受信するcompleteメソッドに分かれていました。しかし、authenticate_with_open_idメソッドはこれらの処理を隠蔽してくれます。そのため、認証処理をシンプルに記述できています。

まず、OPへのOpenIDリクエストは、authenticate_with_open_idメソッドが自動的に生成します。そのため、createアクション側でOPへのリダイレクト処理を書く必要はありません。

次に、OPからのOpenIDレスポンスを受信すると、authenticate_with_open_idの内側に記述したブロックが実行されます。ブロック内部では処理結果を格納したresult変数と、ユーザのOpenIDアカウント (User Claimed Identifier) を格納したidentity_url変数が利用できます。createアクションでは、result.successful? を使って、認証処理が成功したかどうかを確認し、成功の場合はidentity_url からアカウント名を取得します。

このように、OpenID Authenticationプラグインを使えば、認証処理をシンプルに記述できています。createアクションでの処理をまとめると、以下のようになります。

認証成功の場合
  • データベースにユーザが登録されていればセッションを生成してログイン成功(A)
  • データベースにユーザが未登録であればユーザ登録画面へ(B)
認証失敗の場合
  • ログイン画面にエラーメッセージを表示(C)

パスワード認証の場合と異なり、OpenIDではOPでのユーザ認証とRPでのユーザ登録の確認の二段階となります。また、ユーザが未登録の場合にユーザ登録画面に遷移するのも特徴的です。

ユーザ登録処理

前述の通り、OpenIDで最初にログインする時には、RP側にはそのユーザ情報は存在しません。ブログのコメントのように、その場限りの認証であればRPでユーザ情報を持つ必要がありません。しかし、通常のサービスであればRPにもユーザ情報が必要となります。

図3 ユーザ登録画面
図3 ユーザ登録画面

今回のサンプルではつぶやきの画面に表示するニックネームが必要なので、ニックネームを入力してもらい、ユーザ情報をデータベースに登録します。すでに認証済みのユーザを登録するので、メールアドレスの到達確認やCAPTCHAの入力は不要としています。パスワードの登録も必要ありません。

もちろん、アプリケーションの種類によっては、ユーザのメールアドレスが必須の場合もあります。そのときは、通常のユーザ登録のようにメールアドレスの到達確認機能も必要になるかもしれません。また、SREGに対応しているOPであれば、SREGを使ってOPに登録されているメールアドレス情報を取得することもできるでしょう。

ユーザ登録画面から呼ばれるUsersコントローラのcreateメソッドを以下に示します。ユーザ情報のうち、identity_urlを先ほど保存したセッション情報から設定しているのが特徴的です。

controller/users_controller.rb
  def create
    @user = User.new(params[:user])
    # identity_url 属性にセッションで保存した値を代入
    @user.identity_url = session[:identity_url]
    if @user.save
      # ユーザ登録に成功したら home 画面へ移動
      redirect_to(user_home_path)
    else
      render :action => "new" }
    end
  end

ユーザ登録が終わると、ホーム画面へと遷移します。また、次回のログインからは、すでにユーザが登録されているので、認証成功時にホーム画面へと遷移します。

まとめ

今回はOpenID Authenticationプラグインを使って、ログインおよびユーザ認証の機能を作成しました。パスワード認証の場合と異なり、OpenID認証では先にOpenIDを使ってユーザを認証します。認証後にユーザ登録の有無によって、ログイン成功とするかユーザ登録画面を表示するかを制御します。

念のために書いておきますが、ユーザ登録フォームに利用者が直接OpenIDアカウントを入力させてはいけません。OpenID認証を実施して、ユーザのOpenIDアカウントが正当であることが確認できてから、ユーザ情報にOpenIDアカウント名を自動で設定するようにしてください。

次回は今回のサンプルアプリケーションを拡張し、パスワード認証とOpenID認証が共存できるようにします。

おすすめ記事

記事・ニュース一覧