NodeJS + Facebook JavaScript SDKを使ってサーバー側で認証チェックをする


Facebook JavaScript SDKを使えばクライアント側でログイン認証の処理が簡潔に実現できますが、自前のWebアプリのサーバー側で提供するAPIを呼びたい時に、API側でも認証のチェックをした方が良いです。

例えば、DBのエントリを削除するよるREST APIを作ったとして、誰もが消せてしまったら困りますよね。エントリを作成した人のみが削除権限を持つような仕組を実装したい場合は、サーバー上のDELETEのAPIで本当にその人であるかの確認を行う必要があります。今回はJavaScript SDKを使ってログインが完了したユーザーのアクセストークン(FB.getLoginStatusを読んだ時に得られるトークン)をNodeJSのexpress環境で作成したREST APIの中でverifyする例を紹介します。

まずアプリケーションがFacebookのAPIを叩くためにnodeサーバーの起動時にアクセストークンを取得して保存しておきます。

サーバー起動時にFacebookアプリケーションのclient_idとclient_secretを渡してアプリケーション自信の認証を行います。{your_fb_app_client_id}と{your_fb_app_client_secret}を適宜置き換えてください。

var request= require('request');
var url = require('url');
var fbAppAccessToken;
var params = {
    host: 'graph.facebook.com',
    pathname: '/oauth/access_token',
    protocol: 'https',
    query: {
        'client_id': '{your_fb_app_client_id}',
        'client_secret': '{your_fb_app_client_secret}',
        'grant_type': 'client_credentials'
    }
};
request(url.format(params), function (error, response, body) {
    if (!error && response.statusCode == 200) {
        response.setEncoding('utf8');
       fbAppAccessToken = body.split('=')[1];
    } else {
        console.log('error: ' + response.statusCode);
        console.log(body);
    }
});

上記のコードでアプリ起動時にアクセストークンがfbAppAccessTokenという変数にセットされ、FBのAPIを叩けるようになります。
では次にclient側からユーザーのaccessTokenを送りましょう。
まず、javascript SDKを使った際に個々のユーザーのアクセストークンは以下のように取得できていると思います。(わかりやすいようにfbAccessTokenというグローバル変数に保持しておきます)

FB.getLoginStatus(function(response) {
    if(response.status === 'connected'){
        // 各ユーザーのアプリへのFB認証が完了している場合は
        fbAccessToken = response.authResponse.accessToken;
    }
});

クライアント側からnodejs上に用意するDELETEのapiにコールするにはjQueryを使って以下のようにリクエスト。inputTokenというパラメーター名でユーザーのアクセストークンを送ります。

deleteEntry: function(entryId){
    $.ajax({type: "DELETE",
        url: "/api/entry/" + entryId + "/?inputToken=" + fbAccessToken
    }).done(
        // success時の処理
    );
}

node+express側で、DELETEやPOSTのREST APIを定義します。(事前にDBのエントリが認証ユーザーによってPOSTで作成され、administratorというエントリにuserIdが登録されていると想定)

router.delete('/entry/:id', function(req, res) {
    var target = req.param("id");
    var callback = function(authResponse){
        // FBのauthenticationのレスポンスからuserIdを取得
        var userId = authResponse.data.user_id;
        // DBからidとuserIdがマッチするエントリを削除 (NeDB/MongoDBの例)
        req.db.entries.remove({_id: target, administrator: userId}, function(err, numRemoved){
            res.contentType('application/json');
            res.send('{"success":true}');
        });
    };
    fbCheckAccessToken(req.query.inputToken, callback);
});

さて、最後に上記の認証のverify(/debug_token)をするためのfbCheckAccessTokenを実装すれば終わりです。

var fbCheckAccessToken = function(inputToken, callback){
        var params = {
            host: 'graph.facebook.com',
            pathname: '/debug_token',
            protocol: 'https',
            query: {
                // client側から受けとるユーザーごとのアクセストークン
                'input_token': inputToken,
                // 事前に取得したアプリのアクセストークン
                'access_token': fbAppAccessToken
            }
        };
        request(url.format(params), function (error, response, body) {
            if (!error && response.statusCode == 200) {
                response.setEncoding('utf8');
                var resJson = JSON.parse(body);
                if (!resJson.data || !resJson.data.is_valid) {
                    throw new Error('Invalid access token');
                };
                // 認証がvalidであればcallbackを呼ぶ
                if (callback){
                    callback(resJson);
                }
            } else {
                console.log('error: ' + response.statusCode);
                console.log(body);
            }
        });
}

以上で、サーバー側API上でFBユーザーの認証がvalidであると確認でき、そのユーザーのIDを使用した処理ができるようになりました。めでたしめでたし。