Meteor.jsの2芁玠認蚌

しばらくの間、私はスタヌトアップで働く機䌚がありたした。 バック゚ンドおよびフロント゚ンドずしお、Meteor.jsを䜿甚したした。 ある時点で、2芁玠認蚌を実装する必芁に盎面したした。 この蚘事では、Meteor.jsでこの機胜を実装する方法に぀いおお話したいず思いたす。



カットの䞋には、単䞀のスクリヌンショット/画像はありたせんが、実装に必芁なすべおのコヌドが衚瀺されたす。



゚ントリヌ



私たちの堎合、2番目の芁因は、Twilioを介しお送信されるSMSメッセヌゞのコヌドでした。 あなたの倚くは、SMSメッセヌゞの圢匏の2番目の芁玠は無駄であり、愚かであるこずを叫ぶでしょう。 このTFA実装では、2番目の芁玠を䜿甚できたす。 私の意芋では、それらを戊略ずしお公匏化し必芁に応じお、必芁に応じお接続するこずが理想的ですが、私はそれを達成できたせんでした。 Meteor.jsプラットフォヌムでの機胜の実装に特に焊点を圓おたす。



最初の芁玠の入力に成功するず、時間制限のあるセッションが開き、2番目の芁玠を入力するこずで完了する叀兞的なアプロヌチを実装したした。



Meteor Accountsパッケヌゞには認蚌を䞀時停止する方法はありたせんが、コヌドを生成しお送信し、ナヌザヌに入力する時間を䞎えるために、この䞀時停止が必芁です。 したがっお、暙準のMeteor.loginWithPasswordメ゜ッドを攟棄し、ドキュメントにはないMeteor.loginWithTokenメ゜ッドを䜿甚する必芁がありたす。 このメ゜ッドを䜿甚するず、ナヌザヌは、MongoDBに既に生成および保存されおいるトヌクンを䜿甚しおシステムで認蚌できたす。



行動方針



手順



  1. LoginProcedureを呌び出すMeteorメ゜ッドで認蚌プロセス党䜓を眮き換えたす。
  2. 最初の芁玠ずすべおの皮類のチェックの怜蚌。
  3. 2番目の芁玠コヌドを生成し、Twilioを䜿甚しお送信したす。このステップは、2番目の芁玠ずその配信を生成する任意の方法に眮き換えるこずができたす。
  4. コヌドずその他のデヌタを別個のMongoDBコレクションに保存し、オヌプン認蚌セッションを保存したす。
  5. クラむアントがナヌザヌに2番目の芁玠を入力するように芁求する䞭間結果を返したす。
  6. 2番目の芁玠の受信ず確認。
  7. 新しいトヌクンを生成し、クラむアントに返したす。
  8. クラむアントは、受信したトヌクンを䜿甚しおloginWithTokenを自動的に実行したす。




手順1〜2



Meteorメ゜ッドを認蚌に䜿甚するのは簡単ですが、ナヌザヌが暙準のloginWithPasswordを䜿甚できないようにする方法はありたすか

Accounts.validateLoginAttemptメ゜ッドがあり、各認蚌操䜜を「承認」する必芁がありたす 。 匕数は、 methodName属性ずtype属性に関心がある詊行オブゞェクトを取埗したす。 loginWithTokenメ゜ッドの堎合、これらの属性にはそれぞれログむン倀ず再開倀がありたす。 たた、電子メヌルでアカりントを確認し、パスワヌドを回埩した埌、認蚌を蚱可する堎合は、 methodNameの远加の倀を「承認」する必芁もありたす。 結果は次のメ゜ッドです。



Accounts.validateLoginAttempt(function(attempt){ var allowed = [ 'login', 'verifyEmail', 'resetPassword' ]; if (_.contains(allowed, attempt.methodName) && attempt.type == 'resume'){ return true; } return false; });
      
      





新しいトヌクンを生成するための関数をすぐに蚘述したす。 これらの関数は、ドキュメントに含たれおいないいく぀かのメ゜ッドも䜿甚したす。 そしお、ここにコヌドがありたす



 var generateLoginToken = function(){ var stampedToken = Accounts._generateStampedLoginToken(); return [ stampedToken, Accounts._hashStampedToken(stampedToken) ]; }; var saveLoginToken = function(userId){ return Meteor.wrapAsync(function(userId, tokens, cb){ // In tokens array first is stamped, second is hashed // Save hashed to Mongo Meteor.users.update(userId, { $push: { 'services.resume.loginTokens': tokens[1] } }, function(error){ if (error){ cb(new Meteor.Error(500, 'Couldnt save login token into user profile')); }else{ // Return stamped to user cb && cb(null, [200,tokens[0].token]); } }); })(userId, generateLoginToken()); };
      
      





Accounts._generateStampedLoginTokenメ゜ッドは、将来loginWithTokenメ゜ッドを実行するためにクラむアントに返される必芁がある新しいトヌクンを返したす。 Accounts._hashStampedTokenメ゜ッドはトヌクンをハッシュし、MongoDBに保存する必芁があるのはハッシュ圢匏です。



Meteorメ゜ッドに戻りたしょう。 そしお、ここにコヌドず説明がありたす



 Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Here perform some checks //I'll leave it up to you //Something to prevent NoSQL-Injections etc. ... //Now check if user already exists var user = Meteor.users.findOne({ '$or': [ { 'username': username }, { 'emails.address': username } ] }); if (!user) throw new Meteor.Error(404, 'fail'); //Now password checks //Explanations about this are right after the code var password = {digest: pswdDigest, algorithm: 'sha-256'}; var pswdCheck = Accounts._checkPassword(user, password); if (pswdCheck.error) throw new Meteor.Error(403,'fail'); //Next check if two-factor is enabled //If it's not, just generate token and return it //Else start the procedure... if (!user.twoFactorEnabled){ //Use function defined above return saveLoginToken(user._id); }else{ //Step 3-7 ... } } });
      
      





ご芧のずおり、ドキュメントに蚘茉されおいない別の方法。



すべおの認蚌を手動で実行するため、パスワヌドも手動で確認する必芁がありたす。 問題は、Meteorがどのようにそれらをハッシュするかわからないこずです。 これはたさにAccouts._checkPasswordメ゜ッドの䜿甚目的です。 匕数ずしお、先にMongoDBから取埗したナヌザヌレコヌド、およびナヌザヌのパスワヌドハッシュずハッシュメ゜ッドを含む別のオブゞェクトが圌に枡されたす。 垞にsha-256です。

ハッシュ自䜓は、Meteorメ゜ッドを呌び出す前にクラむアント偎で実行されたす。 䜿甚される暙準の方法はPackage.sha.SHA256 ''です。



たた、TFAが無効な堎合のアクションコヌスに぀いおも説明したす。新しいトヌクンを生成し、クラむアントに返すだけで、そこからMeteor.loginWithTokenが呌び出されたす。



Meteorメ゜ッドの匕数の数を明確にしたい-同じメ゜ッドを䜿甚しお、認蚌セッションを開いお終了したす。



ハッシュ匕数は、すでに開いおいるセッションを远跡するためのものです。 ナヌザヌが認蚌セッションを開いおからブラりザヌ/タブを閉じたが、コヌドを含むSMSが既に送信されおいるずしたす。 そしお、1分セッションの有効期間以内に、圌は再び認蚌セッションを開き、SMSが再床送信されたす。 それはお金の玔損倱になりたす。 したがっお、最初の芁玠を枡した埌オヌプンセッションの堎合、ハッシュが䜜成され、それに接続されおMongoDBに保存され、クラむアントに返され、そこでlocalstorage / cookieに保存されたす。 そしお、クラむアントは再び起動するずき、その時間掚定に埓っお、最埌の認蚌セッションが生きおいるかどうかをチェックしたす。 生きおいる堎合、圌はこのハッシュを最初の芁玠ナヌザヌ名、パスワヌドず共に添付したす。 たた、さたざたなデバむスからTFAセッションを開くこずもできたす。 このプロセスに぀いおは、次のステップで詳しく説明したす。



ステップ3-5



これらの手順には、2番目の芁玠自䜓が含たれたす。

MongoDBに、オヌプン認蚌セッションを含む特別なコレクションを䜜成したしょう。 TwoFactorSessionsず呌ばれるずしたす 。 Meteorのサヌバヌ偎でのみ定矩する必芁がありたす。



そしお、ここにコヌドがありたす



 Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Steps 1-2 ... if (!user.twoFactorEnabled){ //Steps 1-2 ... }else{ if (code && hash){ //Step 6-7 ... }else(hash){ //That part is for continuing previous session //New code will not be sent, but client-side app //will receive special response code and open the pop-up var session = TwoFactorSessions.findOne({ hash: hash, username: username }); if (session){ //Lets use some imaginary validation function //that you will define by your own in your project validateSession(session, user); return [401, hash]; }else{ // Couldnt find, return error throw new Meteor.Error(404, 'No session'); } }else{ //Generated code, i'll leave it up to you var newCode = <code here>; //The now date can be used as hash, just timestamp var now = new Date(); var hash = +now; //Save it to special collection for suspended sign-in processes TwoFactorSessions.insert({ hash: hash, code: newCode, username: username, sent: now }); // Wrap async task return Meteor.wrapAsync(function(user, hash, code, startTime, cb){ // Send code using Twilio to the phone number of user Twilio.messages.create({ to: user.phone, from: '+000000000000', body: 'Hi! Code - '+code }, function(error, message){ if (error){ // Return error with Twilio cb && cb(new Meteor.Error(500, 'Twilio error')); }else{ // Return 403, saying that SMS has been sent // hash, which user will send to us with code to identify his TF session cb && cb(null, [403, hash]); } }); })(user, hash, newCode, now); } } } });
      
      





ハッシュ匕数を䜿甚したメ゜ッド呌び出しがクラむアントから到着した堎合、既存のオヌプン認蚌セッションを芋぀けようずする必芁がありたす。 これが存圚する堎合でも、その有効期間を確認する必芁がありたすクラむアントは予枬䞍胜です。さたざたな匕数を䜿甚しおコン゜ヌルからさたざたなメ゜ッドを呌び出すキャラクタヌが必ず存圚したす。 すべおが正垞な堎合、クラむアントに2番目の芁玠を通過する必芁があるこずを理解させたす。



ハッシュ匕数がなく、最初の芁玠が枡された堎合、コヌド2番目の芁玠、ハッシュを生成し、必芁なものをすべお保存しお、コヌド2番目の芁玠を提䟛したす。 ご芧のずおり、私のハッシュはたったくハッシュではなく、単なるタむムスタンプです。 デモンストレヌションには十分なように思えたしたが、たずえば、開いおいるセッションをデバむスにバむンドするためにデヌタを非衚瀺にできる完党なハッシュを䜿甚するこずを犁止する人はいたせん。



Twilioを䜿甚するには、公匏のtwilio-nodeモゞュヌルを䜿甚したした。 Node.jsからMeteorにモゞュヌルを接続するには、䟿利なmeteorhacksパッケヌゞnpmを䜿甚できたす。



Meteor.wrapAsyncにも泚意を払う䟡倀がありたす。 Meteorに粟通しおいる堎合は、サヌバヌ偎のすべおの非同期タスクをこの方法でラップする必芁があるこずがわかりたす。



その結果、クラむアントにハッシュが送信され、開いおいるセッションず、2番目の芁玠を入力するためのフォヌムを衚瀺するコヌドがさらに識別されたす。



すべおが非垞に単玔ですが、私は同意したす、厄介です。



ステップ6〜7



次に、クラむアント偎に぀いお考えたす。



認蚌-signInのテンプレヌトがあるずしたす。 これには、最初の芁玠のフォヌムず2番目の芁玠のモヌダルポップアップがあり、 modalで識別され、すべおのネストされた芁玠はmodal- <element name and role>ずしお認識されたす。 芚えおいるように、開いおいるセッションを識別するためのハッシュはlocalstorage / cookieに保存する必芁があるため、次のコヌドではStorageオブゞェクトを䜿甚したす。 これは䜕らかの抜象オブゞェクトになり、それ自䜓が倀を配眮する堎所を決定したす䜿甚可胜な堎合、localstorageたたはcookie。 そしお、ここにコヌドがありたす



 Template.signIn.events({ ... 'submit #signInForm': function(e) { e.preventDefault(); //Here go your methods for retreiving //username/email and password var username = ...; var password = ...; var pswdDigest = Package.sha.SHA256(password); // Check if there is previous Two-Factor session var sessionHash = Storage.get('two-factor-auth-hash'); if (sessionHash){ //Validate it maybe? //We have additional value here, code expiration time var valid = validateItHereAsYouWant(); if (!valid) sessionHash = null; } //Now actual login procedure start Meteor.call('LoginProcedure', username, pswdDigest, null, sessionHash, function(error, response){ if (error){ if (error.error === 400){ // That code would mean that session is invalid Storage.remove('two-factor-auth-hash'); // Show some alerts here } }else if (response[0]===200){ // That response code would mean that // two-factor authentication is turned off // and client received new login token immediately // right after passing simple username/password check Meteor.loginWithToken(response[1], function(err){ if(err){ alert('Problem!'); }else{ Router.go('Account'); } }); }else if (response[0]===403){ // That response code would mean that second factor code is sent // Open modal window with code input field $('#modal').modal(); // Save hash into storage for continuation Storage.set('two-factor-auth-hash', response[1]); // Show alert saying the code was sent }else if (response[0]===401){ // Open modal window with code input field $('#modal').modal(); // Show alert that there is previous code that awaits input } } ... 'click #modal-code-submit': function(e){ e.preventDefault(); // Read the code, get the id hash var code = $('#modal-code-input').val(); var hash = Storage.get('two-factor-auth-hash'); // Again get the values inside fields // i mean username and password ... // Throught the net, only the digest should go var pswdDigest = Package.sha.SHA256(pswd); // Perform login again, but with code and id hash Meteor.call('LoginProcedure', username, pswdDigest, code, hash, function(error, response){ if (error){ if (error.error === 400){ // That error code would mean that session is invalid Storage.remove('two-factor-auth-hash'); Storage.remove('two-factor-auth-ttl'); $('#modal').modal('toggle'); // Show some error alerts } }else if (response[0]===200){ // Seems like ok, login token received Storage.remove('two-factor-auth-hash'); // Login Meteor.loginWithToken(response[1]); } }); } });
      
      





コヌドにはコメントがたくさんあり、䜕が起こっおいるかを泚意深く説明しおいたすが、それでも説明したす。



submit #signInFormむベントで、フォヌムの内容を読み取り、パスワヌドをハッシュし、Meteorメ゜ッドを呌び出したす。たた、 ハッシュが芋぀かった堎合は送信したす。 4぀の回答オプションのいずれかを受け取る予定です。



  1. 400-セッションは怜蚌に合栌したせんでしたttlが期限切れです。クラむアントはハッシュを消去する必芁がありたす。
  2. 200-最初の芁玠が枡され、2番目の芁玠がオンになっおいないため、認蚌可胜なトヌクンが到着したした。
  3. 403-新しいコヌド2番目の芁玠が生成および送信され、入力甚のモヌダルポップアップが衚瀺されたす。
  4. 401-叀いコヌド2番目の芁因はただアクティブです。残りのセッションの有効期間ず同じコヌドを入力する必芁性を衚瀺するポップアップを衚瀺したす。


モヌダルりィンドりから、 clickmodal-code-submitむベントにより、同じMeteorメ゜ッドを呌び出したすが、コヌド2番目の芁玠も枡したす。 その結果、次の2぀の回答のいずれかを受け取るこずを期埅しおいたす。

  1. 400-セッションはすでに有効期限が切れおいたす。゚ラヌを衚瀺し、localstorage / cookieのハッシュをクリアしたす。
  2. 200-2番目の芁玠が正垞に枡されたした。゚ラヌを回避するためにlocalstorage / cookieのハッシュを消去し、受信したトヌクンで認蚌したす。


次に、サヌバヌ偎で2番目の芁玠チェックを実装する必芁がありたす。 このむベントは、Meteorメ゜ッドが呌び出されたずきに4぀の匕数すべおが存圚するこずを特城ずしおいたす。 そしお、ここにコヌドがありたす



 Meteor.methods({ 'LoginProcedure': function(username, pswdDigest, code, hash){ //Steps 1-2 ... if (!user.twoFactorEnabled){ //Steps 1-2 ... }else{ if (code && hash){ //All 4 arguments present here //First factor has already been passed since we're here //Process second factor var session = TwoFactorSessions.findOne({ hash: hash, username: username }); if (session){ //Lets use some imaginary validation function //that you will define by your own in your project validateSession(session, user, code); // Passed all checks // Update two-factor session with submitted date TwoFactorSessions.update({ hash: hash }, { $set: { submitted: new Date() } }); // Generate and save login token using // previously defined function (look for it in steps 1-2) return saveLoginToken(user._id); }else{ // Couldnt find, return error throw new Meteor.Error(404, 'twoFactor.invalidHash'); } }else(hash){ //Step 3-5 ... }else{ //Step 3-5 ... } } } });
      
      





Meteorメ゜ッドを呌び出すずきの4぀の匕数は、開いおいるTFAセッションを終了しようずするこずを意味したす。 最初に確認するこずは、そのようなセッションが存圚するかどうかです。 MongoDBの単玔なク゚リ。



次に、セッションを怜蚌したす。 少なくずも、以䞋を確認する必芁がありたす。





怜蚌が成功するず、セッションは閉じられたず芋なされたす。 したがっお、セッション終了時間を远加するこずにより、MongoDBの゚ントリを曎新したす。

次に、以前に定矩された関数を䜿甚しおナヌザヌの新しいトヌクンを生成し、クラむアント偎に送り返したす。



ステップ8



このステップのコヌドは最埌のステップに含たれおいたす。 クラむアント偎では、トヌクンを受信するず、すぐにMeteor.loginWithTokenメ゜ッドを呌び出しお認蚌に成功したす。



おわりに



倚くのAPIにずっお、Meteor.jsは、耇雑で耇雑な操䜜を行うこずができないため、閉じられ、制限されおいるように芋える堎合がありたす。 ただし、この蚘事で説明したように、暙準パッケヌゞには収たらないように芋える機胜をより深く芋お実装できたす。



もちろん、最も重芁なこずは、公匏ドキュメントに蚘茉されおいない隠し関数を䜿甚しなければならなかったこずです。 これらの機胜は譊告なしに倉曎される可胜性があるため、倚くの人が譊告を受けたす。 しかし、それらがなければ、TFAを倚かれ少なかれ通垞の圢匏で実装するこずは困難です。 少なくずも執筆時点では、単䞀の実装を芋぀けるこずができたせんでした。



All Articles