web-dev-qa-db-ja.com

NodeJS REST PassportとOAuth2 +ソーシャルネットワークを使用した認証

RESTを使用してNodeJSapiに取り組んでいます。認証にはPassportを使用することにしました。本当にRESTfulなAPIが欲しいです。つまり、セッションの代わりにトークンを使用する必要があるということです。

ユーザーがユーザー名とパスワードを使用して、またはFacebook、Google、Twitterなどのソーシャルネットワークを使用してログインできるようにしたい。

OAuth2.0モジュールを使用してAccessRefresh tokensを発行するための独自のoauth2orizeサーバーを作成します。これで、新しいユーザーを登録して、トークンを発行できます。私はこのチュートリアルに従いました:

http://aleksandrov.ws/2013/09/12/restful-api-with-nodejs-plus-mongodb/

ルートのユーザーの確認:

// api ------------------------------------------------------------------------------------
    app.get('/api/userInfo',
        passport.authenticate('bearer', { session: false }),
        function(req, res) {
            // req.authInfo is set using the `info` argument supplied by
            // `BearerStrategy`.  It is typically used to indicate scope of the token,
            // and used in access control checks.  For illustrative purposes, this
            // example simply returns the scope in the response.
            res.json({ user_id: req.user.userId, name: req.user.username, scope: req.authInfo.scope })
        }
    );

これはすべて非常にうまく機能します。残念ながら、ソーシャル認証を実装する方法がわかりません。

私はこのチュートリアルを読んでいました:

http://scotch.io/tutorials/javascript/easy-node-authentication-facebook 

しかし、このチュートリアルでは、彼らは真にRESTfulなAPIを作成していません。このチュートリアルに従って、ローカルユーザーのトークンが別々のモデルに格納されるユーザースキーマをすでに実装しました。

// define the schema for our user model
var userSchema = mongoose.Schema({
    local: {
        username: {
            type: String,
            unique: true,
            required: true
        },
        hashedPassword: {
            type: String,
            required: true
        },
        created: {
            type: Date,
            default: Date.now
        }
    },
    facebook: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    Twitter: {
        id: String,
        token: String,
        displayName: String,
        username: String
    },
    google: {
        id: String,
        token: String,
        email: String,
        name: String
    }
});

しかし今、どうすればユーザーを確認できますか?

passport.authenticate('bearer', { session: false }),

これは私のデータベースに対してベアラートークンのみを検証していますが、ソーシャルトークンを検証するにはどうすればよいですか?私は何かが足りないのですか?

21
Sandak

私は自分のRESTfulAPIにFacebookログインを使用しています 私のメモ帳アプリはこちら 。 Webページとして使用するアプリケーションとして起動しましたが、ログイン後の通信はAPIを介して行われます。

次に、APIを使用する 同じアプリのモバイルバージョン を作成することにしました。私はそのようにすることにしました:モバイルアプリはFacebook経由でログインし、FacebookユーザーIDとFBアクセストークンをAPIに送信し、APIはFacebookのAPIを呼び出してこれらのパラメーターを確認し、成功した場合は新しいユーザーを登録します(またはログインします既存のもの)私のアプリのDBで、このユーザーのカスタムトークンを作成し、モバイルアプリに返します。ここから、モバイルアプリはこのカスタムトークンを送信して、APIでアプリを認証します。

ここにいくつかのコードがあります:

APIの認証(fbgraph npmモジュールを使用):

var graph = require('fbgraph'),
Promise = require('bluebird')
...
Promise.promisify(graph.get);
...
var postAuthHandler = function (req, res) {
    var fbUserId = req.body.fbId,
    fbAccessToken = req.body.fbAccessToken,
    accessToken = req.body.accessToken;
    ...
    graph.setAppSecret(config.facebook.app.secret);
    graph.setAccessToken(fbAccessToken);

    var graphUser;
    var p = graph.getAsync('me?fields=id,name,picture')
        .then(function (fbGraphUser) {
            //when the given fb id and token mismatch:
            if (!fbGraphUser || fbGraphUser.id !== fbUserId) {
                console.error("Invalid user from fbAccessToken!");
                res.status(HttpStatus.FORBIDDEN).json({});
                return p.cancel();
            }

            graphUser = fbGraphUser;

            return User.fb(fbUserId);
        })
        .then(function (user) {
            if (user) {
                //user found by his FB access token
                res.status(HttpStatus.OK).json({accessToken: user.accessToken});
                //stop the promises chain here
                return p.cancel();
            }
            ...create the user, generate a custom token and return it as above...

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/6617a5cb418ba8acd6351ef9a9f69228f1047154/src/routes/users.js#L46

ユーザーモデル:

var userSchema = new mongoose.Schema({
    facebookId: { type: String, required: true, unique: true },
    accessToken: { type: String, required: true, unique: true },
    name: { type: String, required: true },
    photo: { type: String, required: true },
    categories: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Category' }],
    notepads: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Notepad' }]
});

https://github.com/iliyan-trifonov/notepads-nodejs-angularjs-mongodb-bootstrap/blob/master/src/models/user.js#L9

モバイルアプリのFacebook認証:

               auth: function(fbId, fbAccessToken) {
                return $http({
                    url: apiBase + '/users/auth',
                    data: {
                        fbId: fbId,
                        fbAccessToken: fbAccessToken
                    },
                    method: 'POST',
                    cache: false
                });
            },
            ...

https://github.com/iliyan-trifonov/notepads-ionic/blob/master/www/js/services.js#L

モバイルアプリは、リクエストとともにトークンを送信します。

  notepads: {
            list: function() {
                return $http({
                    url: apiBase + '/notepads?insidecats=1' + '&token=' + User.get().accessToken/*gets the token from the local storage*/,
                    method: 'GET',
                    cache: false
                });
            },

これはIonic/Angular/Cordovaアプリです。モバイルアプリからのFacebookログインは、携帯電話にインストールされているFacebookアプリを起動するか、Facebookにログインするためのポップアップを開きます。次に、コールバックがFacebookユーザーのIDとアクセストークンをモバイルアプリに返します。

Fbgraph npmモジュール: https://github.com/criso/fbgraph

7
Iliyan Trifonov

Iv'eは次のスキーマを作成しました。

var userSchema = mongoose.Schema({
    local: {
        username: String,
        password: String
    },
    facebook: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    google: {
        id: String,
        token: String,
        email: String,
        name: String
    },
    token: {
        type: Schema.Types.ObjectId,
        ref: 'Token',
        default: null
    }
});

var tokenSchema = mongoose.Schema({
    value: String,
    user: {
        type: Schema.Types.ObjectId,
        ref: 'User'
    },
    expireAt: {
        type: Date,
        expires: 60,
        default: Date.now
    }
});

Webアプリにログインするときは、facebook/googleなどのPassportJSSocialプラグインを使用します。例:

//auth.js(router)
router.get('/facebook', passport.authenticate('facebook', {scope: ['email']}));

router.get('/facebook/callback', 
  passport.authenticate('facebook', { successRedirect: '/profile',
                                      failureRedirect: '/' }));

Webアプリケーションをナビゲートするときは、Facebookプラグインを介して利用できるようになったセッション認証を使用します。ユーザーがAPIトークンをリクエストする場合は、ログインする必要があります。そうすれば、そのトークンをユーザーに関連付けることができます。

これで、ユーザーには、APIのトークン認証に使用できるトークンが関連付けられました。

私のAPIルートはセッションを気にせず、見ません。気にするのはトークンだけです。これは、Expressルーターを作成し、パスポートのベアラー戦略をAPIに向かうすべてのルートのミドルウェアとして使用することで実現しました。

//api.js (router)
//router middleware to use TOKEN authentication on every API request
router.use(passport.authenticate('bearer', { session: false }));

router.get('/testAPI', function(req, res){
        res.json({ SecretData: 'abc123' });
    });

そのため、今ではAPIにトークン認証(セッションデータを確認することはありません)とセッション認証のみを使用して、Webアプリを簡単にナビゲートしています。セッションを使用してアプリをナビゲートする例を以下に示します。

//secure.js(router) - Access private but NON-API routes.
//router middleware, uses session authentication for every request
router.use(function(req, res, next){
        if(req.isAuthenticated()){
            return next();
        }
        res.redirect('/auth');
    });
//example router
router.get('/profile', function(req, res){
        res.send("Private Profile data");
    });

うまくいけば、これはあなたを助けるでしょう!

2
Brent Aureli

ベアラートークンを使用している場合は、APIのユーザーに一意の識別子を渡すだけです。これはおそらく、ログインの形で1回の呼び出しで実行されます。次に、APIを呼び出すたびに、トークンがデータベースに存在することを確認し、その存続可能時間の値を更新するトークンが必要です。ログインごとにスキーマに個別のトークンは必要ありません。存続可能時間の値(TTL)を持つトークンに個別のスキーマが必要です。

0
Chacliff

パスポートのソーシャルメディア戦略は、セッションによって異なります。それらがないと機能しません。

RESTサーバーをステートレスにしたかったのと同じ問題で実行しています。

私の意見では、2つのオプションがあります。

  1. レストサーバーにセッションを追加する
  2. トークンの作成と配布を担当する新しい認証サーバーを追加します。

PS:パスポート開発者が見ることができるように、これをパスポートとしてタグ付けする必要があります。

0