Backbonejs + Expressの単一ページアプリケーションでのFacebookおよびVKontakteの承認

こんにちはHabr! この記事では、Backbonejs + Expressの例を使用して、1ページのアプリケーションでソーシャルネットワークを使用して承認を実装する方法について説明します。



Backbone.js





Node.jsがインストールされていない場合は、 off.siteからダウンロードできます。 Expressをインストールするには、Expressアプリケーションジェネレーターを使用します。



npm install express-generator -g express habr cd habr && npm install
      
      





habrと呼ばれる新しいExpressアプリケーションを作成しました。 ビューディレクトリは必要ないので削除し、画像の名前をimgに、javascriptsをjsに、スタイルシートをstyleに変更し、テンプレートが置かれるフォルダーpublic / tplを追加します。 これで、プロジェクトの構造は次のようになります。



 . ├── app.js ├── bin │ └── www ├── package.json ├── public │ ├── img │ ├── js │ ├── tpl │ └── style │ └── style.css ├── routes │ ├── index.js │ └── users.js
      
      





コンポーネントをロードするには、 RequireJSRequireJS / textjsを使用してテンプレートをロードします。 アプリケーションの初期化は、init.jsで実行されます。



RequireJs構成を追加します。



public / js / init.js:



 requirejs.config({ baseUrl: "js/", paths: { jquery: 'lib/jquery.min', backbone: 'lib/backbone.min', underscore: 'lib/underscore.min', fb: 'https://connect.facebook.net/ru_RU/all', //Facebook api vk: 'https://vk.com/js/api/openapi', //Vk API text: 'lib/text', tpl: '../tpl' }, shim: { 'underscore': { exports: '_' }, 'vk': { exports: 'VK' }, 'fb': { exports: 'FB' }, 'backbone': { deps: ['underscore', 'jquery'], exports: 'Backbone' } } });
      
      





VkとFacebook APIを操作するためのライブラリをすぐに追加しました。



Backbonejsには、ルートの前にミドルウェアを呼び出すための機能がないため、 を使用して 2つのメソッドを追加しました。各ルートの前後に呼び出されるbeforeとafterです。 権限のないユーザーがアクセスしてはならないルートを呼び出す前に、承認を確認するためにこれが必要です。



public / js / baseRouter.js:



baseRouter.js
 define([ 'underscore', 'backbone' ], function(_, Backbone){ var BaseRouter = Backbone.Router.extend({ before: function(){}, after: function(){}, route : function(route, name, callback){ if (!_.isRegExp(route)) route = this._routeToRegExp(route); if (_.isFunction(name)) { callback = name; name = ''; } if (!callback) callback = this[name]; var router = this; Backbone.history.route(route, function(fragment) { var args = router._extractParameters(route, fragment); var next = function(){ callback && callback.apply(router, args); router.trigger.apply(router, ['route:' + name].concat(args)); router.trigger('route', name, args); Backbone.history.trigger('route', router, name, args); router.after.apply(router, args); } router.before.apply(router, [args, next]); }); return this; } }); return BaseRouter; });
      
      





次に、ルートを定義します。



public / js / router.js:



 define([ 'baseRouter', ], function(BaseRouter){ return BaseRouter.extend({ routes: { "secure": "secure", "login" : "login" }, //        secure_pages: [ '#secure' ], before : function(params, next){ next(); }, secure: function(){ console.log('This is secure page'); }, login: function(){ console.log('This is login page'); } }); });
      
      





ファイルpublic / tpl / index.htmlを作成し、許容できるようにbootstrap.cssを接続します。



 <!DOCTYPE html> <html> <head> <title></title> <script data-main="/js/init" src="js/lib/require.js"></script> <link rel="stylesheet" href="/style/bootstrap.min.css"/> </head> <body> <div class="container"> <nav class="navbar navbar-default"> <div class="container-fluid"> <ul class="nav navbar-nav"> <li><a href="#">Home</a></li> <li><a href="#secure">Secure</a></li> </ul> <ul class="nav navbar-nav navbar-right"> <li><p class="navbar-text">   </p></li> <li><a href="#login">Login</a></li> </ul> </div> </nav> <div id="main"></div> </div> </body> </html>
      
      





app.js.ファイルを修正します 余分な機能をファイルに積まないように、この例では不要なコードを削除しました。 app.jsは次のようになります。



app.js
 var express = require('express'); var path = require('path'); var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var app = express(); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); //Routes app.get('/', function(req, res, next) { res.sendFile(path.join(__dirname, 'public/tpl/index.html')); }); // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); err.status = 404; next(err); }); // development error handler if (app.get('env') === 'development') { app.use(function(err, req, res, next) { res.status(err.status || 500); res.json({ message: err.message, error: err }); }); } // production error handler app.use(function(err, req, res, next) { res.status(err.status || 500); res.json({ message: err.message, error: err }); }); module.exports = app;
      
      







そして、init.jsにアプリケーションのロードを追加します。



 require([ 'backbone', 'router', ], function(Backbone, Route){ //      var appRoute = new Route(); Backbone.history.start(); });
      
      





アプリケーションを起動し、何が起こったかを確認します。 ログインページのビューを作成します。



public / js / login_view.js



 define([ 'backbone', 'text!tpl/login.html', //   'vk', //Vk Api 'fb' //Fb Api ], function(Backbone, Tpl, VK, FB){ return Backbone.View.extend({ initialize: function () { this.render(); }, events: { 'click #fb_login' : 'fb_login', 'click #vk_login' : 'vk_login' }, fb_login: function(e){ e.preventDefault(); }, vk_login: function(e){ e.preventDefault(); }, render: function(){ this.$el.html(Tpl); } }); });
      
      





ログインページのテンプレートを追加します。



 <h3>Login</h3> <a href="" id="fb_login">   Facebook</a> <br> <a href="" id="vk_login">   Vkontakte</a>
      
      





FacebookログインAPI







Facebook apiを介して承認するには、アプリケーションを作成する必要があります。 すでに作成していますが、簡単な指示に従って参照することで作成できます。



APIへの接続を初期化します。



public / js / login_view.js:



 initialize: function () { FB.init({ appId: ID , cookie: true, oauth: true}, function(err){ console.log(err); }); this.render(); });
      
      





ブラウザでページを更新すると、コンソールにエラーが表示されます。



URLがブロックされています:リダイレクトできません。URIはクライアント設定アプリケーションのホワイトリストにありません。 クライアントとWeb OAuthログインが有効になっていることを確認し、有効なOAuth URIリダイレクトドメインとしてアプリケーションを追加します。


これは、ドメインをアプリケーション設定に追加していないためです。 有効なURLのリストにlocalhost:3000 /を追加しましょう。 これを行うには、アプリケーションの設定に移動し、「Facebook経由でログイン」、「OAuthリダイレクションの有効なURL」フィールドにlocalhost:3000 /を追加し、保存をクリックします。



次に、Facebook APIにログインする必要があります。 これを行うには、ログインメソッドを呼び出します。このメソッドは、最初の引数と権限オブジェクトとして関数calbackを使用します。 基本情報とメールユーザーをリクエストします。



public / js / login_view.js:



 fb_login: function(e){ e.preventDefault(); FB.login(function(res) { console.log(res); }, { scope: 'public_profile,email'} ); },
      
      





ページを更新し、「Facebookを使用してログイン」をクリックすると、ウィンドウが表示され、Facebookでアプリケーションへの入り口を確認するように求められます。 確認後、ブラウザコンソールでAPIからの応答を確認できます。 statusパラメーターとauthResponse.accessTokenに関心があります



ステータス-現在のユーザーのステータス。 可能な値:





accessToken-アクセストークン。今後使用します。



ステータスハンドラを追加して、現在のユーザーについて必要な情報を取得しましょう。



 fb_login: function(e){ e.preventDefault(); FB.login(function(res) { if (res.status === 'connected') { var fields = ['id', 'first_name', 'last_name', 'link', 'gender', 'picture', 'email']; FB.api('/me?fields=' + fields.join(','), function(res) { console.log(res); }); } }, { scope: 'public_profile,email'} ); },
      
      





コンソールにログインすると、リクエストしたデータオブジェクトが表示されます。 ここで入手できる情報の詳細をお読みください



素晴らしい。 Facebookからユーザー情報を取得しましたが、クライアント側では特に有用ではありません。 サーバー側でユーザーを認証し、データベースにユーザーに関するデータを書き込みたいと思います。



サーバーからリクエストを送信するには、少し前に受け取ったaccess_tokenが必要です。 サーバーに送信しましょう:



 fb_login: function(e){ e.preventDefault(); FB.login(function(res) { if (res.status === 'connected') { $.ajax({ url: '/auth/facebook', method: 'POST', data: { accessToken: res.authResponse.accessToken }, dataType: 'JSON', success: function(res){ console.log(res); } }); } }, { scope: 'public_profile,email'} ); },
      
      





サーバー上で、Facebookから情報を要求します。



app.js:



 app.post('/auth/facebook', function(req, res, next){ var accessToken = req.body.accessToken; var profileFields = ['id', 'first_name', 'last_name', 'link', 'gender', 'picture', 'email']; var request = require('request'); request({ url: 'https://graph.facebook.com/me?access_token=' + accessToken + '&fields=' + profileFields.join(','), method: 'GET', json: true },function (error, response, body) { /** *      */ res.cookie.login = 'test'; res.cookie.hash = 'test'; res.json(body); }); });
      
      





認証のさらなるデモンストレーションのために、ログインとハッシュをCookieに保存しました。 リクエストを送信するとき、json文字列ではなくjavascriptオブジェクトを取得するためにjson:trueを指定する必要があります。 アプリケーションを再起動してログインし、ブラウザコンソールで回答を確認します。 素晴らしい。 すべてが正常に機能します。



Vkontakte APIによる承認。







Vkontakteを介した承認は、Facebookとそれほど違わないため、あまり詳しく説明しません。 ここで承認アプリケーションを作成します 。 VK APIへの接続を開始します。



 VK.init( { apiId: ID  },function(res) { console.log('success'); }, function(res) { console.log('error'); }, '5.53');
      
      





ログイン (ログインメソッドの2番目のパラメーター、 取得する権利を示す番号を渡します)。



 vk_login: function(e){ e.preventDefault(); VK.Auth.login(function(res){ console.log(res); }, 4194304 ); },
      
      





コンソールを見て、答えを確認します。 ここには、ステータスパラメータとsig(access_token)+ユーザーに関する情報を含むユーザーオブジェクトもあります。



さらに、すべてがFacebookほどスムーズにはいきません。







問題1



受信したトークン(sig)はip-addressに添付され、サーバーで使用しようとすると、 「ユーザー認証に失敗しました:access_tokenが別のip addresに与えられました」というエラーが表示されます。 クライアント側でトークンを受信すると、サーバーでトークンを使用できなくなります。



この状況で最も興味深いのは、同じIPで開発およびテストするかどうかを検出するのがそれほど簡単ではないことです。 問題は、戦闘サーバーでのみ発生します。



インターネットには、スコープ内で許可を「オフライン」に指定する必要があるという神話があります。その場合、トークンは「永久」になり、IPに結び付けられません。 ただし、この方法では、IPアドレスへのバインドは削除されません。



オフライン(+65536) いつでもAPIにアクセスします(このオプションを使用すると、access_tokenで返されるexpires_inパラメーターには0が含まれます-トークンは無制限です)。


問題2



この認証方法では、ユーザーのメールを取得する方法はありません。必要な権限をリクエストしてユーザーが同意しても、応答でメールを受信することはありません。



ドキュメントvk.com/dev/authcode_flow_userで説明されているサーバー認証では、スコープ内で電子メールを指定すると、トークンとともに返されます。 オープンAPIを使用する場合、電子メールアドレスにはトークンが付属していません。 技術サポートに目を向けると、答えが返ってきました。



サポートエージェント#1605

現時点では、Open APIを使用してOAuth認証を使用している場合にのみ、電子メールを受信する機能が提供されますが、これは機能しません。


になる方法



サーバー上のクライアントで受信したトークンを使用できないため、サーバーからユーザーに関する情報を要求することはできませんが、トークンの有効性を確認し、このトークンを所有しているユーザーのIDを見つけることができます。



ドキュメントから、次の行を連結すると、sigパラメーターがmd5であることがわかります。





オープンAPIを介してユーザー情報を取得し、サーバーに転送してトークンを確認し、すべてが問題ない場合はデータベースに書き込みます。



 vk_login: function(e){ e.preventDefault(); VK.Auth.login(function(res){ if (res.status === 'connected') { var data = {}; data = res.session; var user = {}; user = res.session.user; VK.Api.call('users.get', { fields: 'sex,photo_50' }, function(res) { if(res.response){ user.photo = res.response[0].photo_50; user.gender = res.response[0].sex; data.user = user; $.ajax({ url: '/auth/vk', method: 'POST', data: data, dataType: 'JSON', success: function(res){ console.log(res); } }); } }); } }, 4194304 ); },
      
      





md5ハッシュを作成するには、cryptoを使用します。



 npm install crypto
      
      





app.js:



 app.post('/auth/vk', function(req, res, next) { var secretKey = '( . )( . )'; //   var sig = req.body.sig, expire = req.body.expire, mid = req.body.mid, secret = req.body.secret, sid = req.body.sid, user = req.body.user; var str = "expire=" + expire + "mid=" + mid + "secret=" + secret + "sid=" + sid + secretKey; var hash = crypto.createHash('md5').update(str).digest('hex'); //  if(hash == sig){ /** *     ,  ,   . */ res.cookie.login = 'test'; res.cookie.hash = 'test'; res.json({ success: true }); } else { res.json({ success: false }); } });
      
      





これで、アプリケーションは来たユーザーのトークンとIDをチェックし、このデータに基づいてサーバー上のユーザーを認証できます。



認可チェック



ユーザー情報を含むモデルを作成しましょう:



public / js / models / user.js:



 define([ 'backbone' ], function(Backbone){ var User = Backbone.Model.extend({ url: '/auth/getUser', initialize: function(){ console.log('user model was loaded'); //  .  -  -  auth this.on('change', function(){ if(this.has('login')){ this.set('auth', true); } }); }, defaults: { auth: false }, isAuth: function(){ return this.get('auth'); }, logout: function(){ //   this.clear(); //    $.post( "/auth/logout" ); } }); return new User(); });
      
      





アプリケーションを起動する前に、ユーザーモデルをロードしましょう。



public / js / init.js:



 require([ 'backbone', 'router', 'models/user' ], function(Backbone, Route, User){ //      User.fetch().done(function(){ var appRoute = new Route(); Backbone.history.start(); }); });
      
      





そして、router.jsにチェックを追加します。



router.js
 define([ 'baseRouter', 'views/login_view', 'models/user' ], function(BaseRouter, LoginView, User){ return BaseRouter.extend({ initialize: function(){ //  this.model = User; //   auth,      this.listenTo(this.model, 'change:auth', function(){ Backbone.history.loadUrl(); }); }, routes: { "" : "index", "#" : "index", "secure": "secure", "login" : "login", "logout": "logoute" }, //     secure_pages: [ '#secure' ], before : function(params, next){ //  var path = Backbone.history.location.hash; //       ? var needAuth = _.contains(this.secure_pages, path); if(path == '#login' && User.isAuth()){ this.navigate("/", true); }else if(!User.isAuth() && needAuth){ this.navigate("login", true); } else { next(); } }, index: function(){ $('#main').html('Index page'); }, secure: function(){ $('#main').html('Secure page'); }, login: function(){ $('#main').html( new LoginView().el ); }, logoute: function(){ this.navigate("/", true); this.model.logout(); } }); });
      
      







サーバー上のユーザー情報を取得するためのルートを追加します。



 app.get('/auth/getUser', function(req, res, next){ /** *     */ if(res.cookie.login == 'test' && res.cookie.hash == 'test'){ res.json({ login: 'text', hash: 'text' }); } else { res.send({}); } });
      
      





およびログアウトルート:



 app.post('/auth/logout', function(req, res, next){ res.cookie.login = ''; res.cookie.hash = ''; });
      
      





最後のタッチにuser_viewを追加します。ここでは、ユーザーに関する情報をヘッダーに表示します。



public / js / views / user_view.js:



 define([ 'backbone', 'text!tpl/user.html' ], function(Backbone, Tpl){ return Backbone.View.extend({ tpl: _.template(Tpl), initialize: function(){ this.render(); //  ,  -  -  this.listenTo(this.model, 'change:auth', function(){ this.render(); }); }, events: { //    'click #logout':'logout' }, logout: function(e){ e.preventDefault(); //  this.model.logout(); }, render: function(){ this.$el.html( this.tpl({ user:this.model.toJSON() })); } }); });
      
      





user_viewのテンプレート:



public / tpl / user.html:



 <ul class="nav navbar-nav navbar-right"> <li> <p class="navbar-text">   <%= user.auth ? user.login.toUpperCase() : '' %></p> </li> <li> <% if(user.auth){ %> <a href="" id="logout">Logout</a> <% } else {%> <a href="#login">Login</a> <% } %> </li> </ul>
      
      





index.htmlを変更します。



 <!DOCTYPE html> <html> <head> <title></title> <script data-main="/js/init" src="js/lib/require.js"></script> <link rel="stylesheet" href="/style/bootstrap.min.css"/> </head> <body> <div class="container"> <nav class="navbar navbar-default"> <div class="container-fluid"> <ul class="nav navbar-nav"> <li><a href="#">Home</a></li> <li><a href="#secure">Secure</a></li> </ul> <div id="user-info"></div> </div> </nav> <div id="main"></div> </div> </body> </html>
      
      





アプリケーションを起動して喜ぶ。



» Githubのソース。



All Articles