セッションの罠、Rails4でiPhoneアプリのログイン認証をやってみた

みなさんこんにちは。
エンジニアのイシマルです。

前回のプランニングポーカーの記事から約2ヶ月、
今回はその間に起きた、とある事件について、記事にしたいと思います。

■事件の種「iPhoneアプリからサーバ(Rails4)で認証してログインする機能」

ログイン、Webアプリでもよくある機能で、iPhoneアプリでもユーザーを認証するのにログイン機能を作ることはよくあります。
今回は、そのログイン認証に、セッション(cookie)を用いて行うことにしました。

今回試した認証は、Facebookログインで、以下の設計で実装することにしました。

  1. 【アプリ】サーバへログインAPIを叩く(パラメータで、FacebookのAccessTokenを付与し、SSL通信)
  2. 【サーバ】は、受け取ったAccessTokenを用いて、FacebookAPIで、そのユーザーのFacebook IDを取得
  3. 【サーバ】usersテーブルで、該当するFacebook IDのユーザーを抽出
  4. 【サーバ】セッションに、user_idを保存
  5. 【サーバ】セッションIDを、responseHeader(Set-Cookie)に入れて、アプリにAPIの戻り値を返す(Railsが勝手にやる)
  6. 【アプリ】responseHeaderで受け取ったcookie情報を以降付与して、サーバと通信
この設計を元に、サーバで実装をまず開始。

ブラウザで、以下のようなAPIを実行し、正しく認証されることを確認した後に、アプリ側SEのKingさんに iPhoneアプリ側の実装をお願いしました。

https://192.168.100.1/api/login?facebook_access_token=hoge
APIの通信は、これらのパラメータの他に、独自の暗号化も行っています。

しばらく時間が経ったあと、Kingさんから、

King「セッションCookieの情報を取得できない。」

と連絡が来ます。

私の方は、ブラウザでセッションCookieがresponseHeaderに入っているのを確認していたので、

「そんなはずはない!ほら、このブラウザで実行した結果からわかるようにセッションCookieがセットされて返ってる!アプリ側の問題じゃないんですか?」

responseHeader
と返しました。

しばらく時間が経ったあと、またKingさんから、

King「やっぱりCookieの情報を取得できない!」

と連絡が来ます。

私は「おかしいな」と思い、Kingさん側のデバッグした情報を見せてもらいましたが、 確かにセッションCookieの情報がresponseHeaderに入っていないんです。

そこで、我らが編集長、キクチさんに相談させていただいたところ、

どうも、サーバ側(Rails側)の問題ということが判明。

私の心の声「ナンテコッタ・・・」

Rails4では、ApplicationController(ベースとなるコントローラ)で、CSRFの対策が施されています。そこに、APIで使うなら、

for_csrf
protect_from_forgery with: :null_session
のように書けば良いですよ、と書かれているので、素直にこの通りに採用していました。
しかし、セッションを空にしていたのは、どうやらコイツの仕業だったのです orz。

クラス仕様:
http://api.rubyonrails.org/classes/ActionController/RequestForgeryProtection/ClassMethods.html

ここに、はっきりこう書かれていますね。。。

:null_session - Provides an empty session during request but doesn't reset it completely. Used as default if :with option is not specified.

結局、こう書き換えて、無事解決しました。

class ApiController < ApplicationController
  skip_before_action :verify_authenticity_token

今回は、自戒を込めて、記事にさせていただきました。
問題がある時は、まず自分を疑うのが良いですね。

では、また次回まで!