9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

sails.jsAdvent Calendar 2014

Day 13

Sails.js(0.10.x)でFacebookログインを実装する

Last updated at Posted at 2014-12-12

追記

Sails.jsを0.11系にすると動かなかった+たぶん色々間違っていたのでコードを修正。0.11系から(?)MiddlewareでPasportを初期化しないと動かない。

config/http.js
module.exports.http = {
  middleware: {
    order: [
      'startRequestTimer',
      'cookieParser',
      'session',
      // Customized Start
      'passportInit',
      'passportSession',
      // Customized End
      'bodyParser',
      'handleBodyParserError',
      'compress',
      'methodOverride',
      'poweredBy',
      '$custom',
      'router',
      'www',
      'favicon',
      '404',
      '500'
    ],
    passportInit: require('passport').initialize(),
    passportSession: require('passport').session()
  }
}

そしてリクエストを受けるControllerはこんな感じ。

api/AuthController.js
/**
 * AuthController
 *
 * @description :: Server-side logic for managing auths
 * @help        :: See http://links.sailsjs.org/docs/controllers
 */

var passport = require('passport');
var FacebookStrategy = require('passport-facebook').Strategy;

passport.use(new FacebookStrategy({
	clientID: process.env.FACEBOOK_CLIENT_ID || 'xxxxx',
	clientSecret: process.env.FACEBOOK_CLIENT_SECRET || 'xxxxx',
	callbackURL: '/auth/facebook/callback'
},
function(accessToken, refreshToken, profile, done) {
	var id = profile.id;
	var username = profile.displayName;
	var iconPath = 'https://graph.facebook.com/' + id + '/picture?width=320&height=320';

	UserService.signInOrSignUpFacebook({
		facebookId: id,
		name: username,
		iconPath: iconPath,
		accessToken: accessToken
	}).then(function(user) {
		return done(null, user);
	}).catch(function(error) {
		sails.log.error(error);
		return done(error);
	});
}));

passport.serializeUser(function(user, done) {
	done(null, user.id);
});

passport.deserializeUser(function(userId, done) {
	User.findOne({id: userId}).then(function(user) {
		done(null, user);
	}).catch(function(error) {
		done(error);
	});
});


module.exports = {
	facebook: function(req, res, next) {
		passport.authenticate('facebook', {})(req, res);
	},
	facebookCallback: function(req, res, next) {
		passport.authenticate('facebook', {
			failureRedirect: '/',
		}, function(error, user) {
			if (error) {
				sails.log.error(error);
				res.redirect(500);
			}

			req.login(user, function(error) {
				res.redirect('/');
			});

		})(req, res);
	}
};

ユーザ情報の取得API

api/UserController.js
module.exports = {
	me: function(req, res) {
		var user = req.user;

		if (!user) {
			return res.send(400);
		}

		res.send(user);
	},
}

Sails.jsを使って、少しだけ拡張性を考えたFacebookログイン方法を書いています。

レポジトリにもコードを置いていますので、コチラも合わせて御覧ください。
https://github.com/KeitaMoromizato/sails-facebook-auth

sails.js Advent Calendar 2014、もうこれでネタ切れです...

注意

node.jsのバージョンが0.11系だと(今のところ)動きません。

Facebook Appの準備

Facebook Developerにアクセス。[Apps] -> [Add a New App]からウェブサイトを選択する。アプリ名を選択して[Create New Facebook App ID]。カテゴリ選べとか言われるけど適当でいいです。

1.png

今回はローカルで動作確認するので、サイトURLはlocalhostに。
(※URLはポート番号まで含めたもの[http://localhost:1337/]が必要でした。)
2.png

次のステップで「ログイン」を選択。

3.png

[Apps]から先ほど作成したアプリケーションを選択。

4.png

App IDとApp Secretを入手します(Secretは[show]をクリックすると表示されます)。

6.png

実装

Sails.jsアプリケーションを作成し、必要なモジュールをインストール。今回はPassport.jsという認証を請け負ってくれるモジュールを使います。Passport.jsはStrategy(認証方法)を色々なものに切り替えることが出来ます。今回はFacebookAuthを使うので、passport-facebookも一緒にインストール。

$ sails new facebook-auth-test
$ cd facebook-auth-test
$ npm install passport --save
$ npm install passport-facebook --save

それぞれ必要なController/Modelを作成します。

$ sails generate api user
$ sails generate controller auth
$ sails generate controller page

Facebookモジュール

ログイン用モジュールです。昨今のサービスだとFacebook/twitter/googleなど多様なサービスでログインすることが出来ますね。そのような拡張性も考え、Signinというモジュールにfacebookログイン用関数を書いています。

api/services/Signin.js
module.exports = (function(){
  var passport = require('passport');
  var FacebookStrategy = require('passport-facebook').Strategy;

  passport.use(new FacebookStrategy({
      clientID : "xxxxxxxxxxxxxxxxxxxxxxxx",
      clientSecret: "xxxxxxxxxxxxxxxxxx",
      callbackURL: "/auth/facebook/callback"
    },
    function(accessToken, refreshToken, profile, done) {

      var id = profile.id;
      var username = profile.displayName;
      var icon = 'https://graph.facebook.com/' + id + '/picture?width=320&height=320';

      User.signinOrSignup("facebook", id, {name: username, icon: icon}, function(err, user) {
        return done(err, user);
      });
    })
  );

  var facebook = function(req, res, callback) {
    passport.authenticate('facebook', {}, function(err, user) {
      if (user) {
        req.session.authenticated = true;
        req.session.user_id = user.id;
      }

      callback(err, user);
    })(req, res);
  };

  return {
    facebook: facebook
  };
})();

地味に重要なところがここ。認証済みフラグ。

req.session.authenticated = true;

ログインAPI

ログイン用APIです。特に複雑なことはなく、ログインでエラーが発生したら/loginにリダイレクトし、成功した場合はルートに入る。

api/controllers/AuthController.js
module.exports = {
  facebook : function(req, res) {

    SignIn.facebook(req, res, function(err, user) {
      if (err) {
        res.redirect('/login');
      }
      else {
        res.redirect('/');
       }
    });
  },
  facebookCallback : function(req, res) {
    passport.authenticate('facebook', {
      successRedirect: '/',
      failureRedirect: '/login' }
    );
  }
};

Userモデル

ユーザモデル。attributesのポイントとしては、認証情報(auth)を配列で持っている事。先ほども少し触れたように、将来的に別の方法でもログイン出来るようにと考えた結果こうなりました。signinOrSignup()は面倒なので登録/ログインを同時にしちゃった関数。

api/models/User.js
module.exports = {

  attributes: {
    auth: 'json',
    name: {
      type: 'string'
    },
    icon: {
      type: 'string'
    }
  },
  signinOrSignup : function(auth, id, data, callback) {
    var query = {};
    query["auth." + auth] = id;
    User.findOne(query).exec(function(err, user) {
      if (err) {
        return callback("ERROR, find User");
      }

      if (user) {
        return callback(null, user);
      }

      // SignUp
      data.auth = {};
      data.auth[auth] = id;
      User.create(data).exec(function(err, user) {

        if (err) {
          return callback("ERROR, create User");
        }

        return callback(null, user);
      });
    });
  }
};

公開/非公開設定

どのページ/APIを公開するかという設定はpolicies.jsに書きます。一番最初にワイルドカードですべて認証必須にし、その後にログイン無しでも公開するController/Actionについてtrueにしていきます。
ちなみに最初に設定しているsessionAuthは、api/policies/sessionAuth.jsです。つまり、APIごとに認証方法も変えられるというわけ。どこで使うかはわかりませんけど。

config/policies.js
module.exports.policies = {

  '*' : 'sessionAuth',

  PageController : {
    login : true
  },

  AuthController : {
    facebook : true
  }
};

ページの表示

基本的にページを表示するだけなら実装は何もいらないはずですが、policiesによるフィルタをかけるばあい、Controllerを経由しないと正常に動きません。(Controllerを通さずに出来る方法あれば教えて下さい!)
なのでログインページとログイン後ページを表示するActionを追加。ついでにログイン後にはFacebookから取ってきたユーザ名とアイコンを表示しましょう。

api/controllers/PageController.js
module.exports = {
  login: function(req, res) {
    res.view('login');
  },
  index: function(req, res) {
    var userId = req.session.user_id;

    User.findOne(userId).exec(function(err, user) {
      if (err) res.send(500);

      res.view('index', {
        user: user
      });
    });
  }
};

後は細々したものを

config/routes.js
  'GET /': {
    controller : 'Page',
    action : 'index'
  },

  'GET /login' : {
    controller : 'Page',
    action : 'login'
  },
views/index.ejs
<div>
  <h1><%= user.name %></h1>
  <img src='<%= user.icon %>' />
</div>
views.login.ejs
<h2>Facebook Login</h2>
<a href="/auth/facebook">Login</a>

動かしてみる

まずはlocalhost/にアクセスしてみると...
なんか陽気なForbiddenが出ました。

7.png

それならとlocalhost/loginから[Login]をクリックすると...

8.png

Facebookに遷移して認証後、無事localhost/が表示されました!

9.png

9
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
9

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?