[CouchDB][CouchApp] CouchApp におけるログイン処理の詳細

認証の話は別の媒体で書くとして、実際どうやってログイン処理をやるの?という話。CouchApp を入れれば、とりあえずOKです。

CouchDBの認証はbasic認証で提供されておりますが、認証ダイアログを出す attemptLogin(win, fail) と 現在認証がすんでいるかどうかを判定する loggedInNow(loggedIn, loggedOut) という2つのメソッドが用意されています。

http://github.com/jchris/couchapp/blob/1d6b96a0535364c61b356d68e08161581339603f/vendor/couchapp/_attachments/jquery.couchapp.js

        attemptLogin : function(win, fail) {
          // depends on nasty hack in blog validation function
          db.saveDoc({"author":"_self"}, { error: function(s, e, r) {
            var namep = r.split(':');
            if (namep[0] == '_self') {
              login = namep.pop();
              $.cookies.set("login", login, '/'+dbname)
              win && win(login);
            } else {
              $.cookies.set("login", "", '/'+dbname)
              fail && fail(s, e, r);
            }
          }});
        },
        loggedInNow : function(loggedIn, loggedOut) {
          login = login || $.cookies.get("login");
          if (login) {
            loggedIn && loggedIn(login);
          } else {
            loggedOut && loggedOut();
          }
        },

で、attemptLogin が面白くて、コメントにあるとおり、CouchDBのvalidation機能をhackしています。blog validation ってなんやねん?という話があるのですが、これは、CouchDBで実装されたSofaというブログツール(http://github.com/jchris/sofa/tree/master) に答えがあって、validate_doc_update.js を見ます。このJSは、ドキュメントの更新(HTTP PUT|POST|DELETE)が発生したときに呼び出される検証用スクリプトです。

http://github.com/jchris/sofa/blob/ac978f63ee79dee1ea67f27d61197fa4c3cde60e/validate_doc_update.js

function (newDoc, oldDoc, userCtx) {
  var type = (oldDoc || newDoc)['type'];
  var author = (oldDoc || newDoc)['author'];
 
  function forbidden(message) {
    throw({forbidden : message});
  };
  ...
  // docs with authors can only be saved by their author
  if (author) {
    // dirty hack to provide userCtx.name to the client process
    if (author == '_self') userCtx.name ? forbidden('_self:' + userCtx.name) : unauthorized('Please log in.');
    

が答えで、PUTされたドキュメントに { "author" : "_self" } が仕込まれていたら、userCtx.name (実際はリクエストヘッダAuthorizationに含まれる認証済みユーザー名) を見て、あれば(認証が成功していれば)、forbiddenを返します。validate_doc_update.js 中にthrowされるとCouchDBは 403 Forbbiden を返してDB自体は更新しませんが、_self:"認証済みユーザー名" という文字列を返すので、その文字列をparseしてクライアント側でクッキーにユーザー名をセットしています。

一方、userCtx.nameがない(認証が成功していない)場合は、401 Unauthorized が返されますので、ブラウザの挙動により、認証用のダイアログが表示され、再度Authorizationヘッダーをつけたリクエストが発行される、ということになります。CookieをセットするのがクライアントのJavaScriptという発想。認証はサーバーでやるべきで、それはサーバーでやるけれど、認証の情報を記憶するのはクライアントが決めればいいよね、とClientでできることはClientでやる姿勢がすばらしい。

というわけで、CouchAppを使ってログインをしたい場合、

  • validate_doc_update.js に { author: "_self" } の場合の検証ロジックを記述する(上記、Sofaのコード参照)
  • ログインを実装するページ上で、attemptLogin を呼び出す (A)
  • ログイン名を表示するページでは、loggedInNow を呼び出す (B)

とまとめらます。attemptLogin とかの使い方は、単なるJavaScriptの話ですが、ページのロード時に呼び出せばよくて、CouchAppの流儀的には以下のようにするようです。$.CouchApp.(function(app){ // ページロード時に実行するコード }) としておけば

<script type="text/javascript">
 $.CouchApp(function(app) {
    app.attemptLogin(
       function(login){
       // ログイン成功時 login に認証済みのユーザー名が入る
       },
       function(){
       // ログイン失敗時
       }
   );
 });
</script>
<script type="text/javascript">
 $.CouchApp(function(app) {
    app.loggedInNow(
       function(login){
       // ログイン済みの場合 login に認証済みのユーザー名が入る
       },
       function(){
       // ログインしていない場合
       }
   );
 });
</script>

しかし、これはワクワクする。Rails以来の面白さだ。