ã«ããã®äžã«ã¯ãåäžã®ã¹ã¯ãªãŒã³ã·ã§ãã/ç»åã¯ãããŸããããå®è£ ã«å¿ èŠãªãã¹ãŠã®ã³ãŒãã衚瀺ãããŸãã
ãšã³ããªãŒ
ç§ãã¡ã®å Žåã2çªç®ã®èŠå ã¯ãTwilioãä»ããŠéä¿¡ãããSMSã¡ãã»ãŒãžã®ã³ãŒãã§ããã ããªãã®å€ãã¯ãSMSã¡ãã»ãŒãžã®åœ¢åŒã®2çªç®ã®èŠçŽ ã¯ç¡é§ã§ãããæãã§ããããšãå«ã¶ã§ãããã ãã®TFAå®è£ ã§ã¯ã2çªç®ã®èŠçŽ ã䜿çšã§ããŸãã ç§ã®æèŠã§ã¯ãããããæŠç¥ãšããŠå ¬åŒåãïŒå¿ èŠã«å¿ããŠïŒãå¿ èŠã«å¿ããŠæ¥ç¶ããããšãçæ³çã§ãããç§ã¯ãããéæã§ããŸããã§ããã Meteor.jsãã©ãããã©ãŒã ã§ã®æ©èœã®å®è£ ã«ç¹ã«çŠç¹ãåœãŠãŸãã
æåã®èŠçŽ ã®å ¥åã«æåãããšãæéå¶éã®ããã»ãã·ã§ã³ãéãã2çªç®ã®èŠçŽ ãå ¥åããããšã§å®äºããå€å žçãªã¢ãããŒããå®è£ ããŸããã
Meteor Accountsããã±ãŒãžã«ã¯èªèšŒãäžæåæ¢ããæ¹æ³ã¯ãããŸããããã³ãŒããçæããŠéä¿¡ãããŠãŒã¶ãŒã«å ¥åããæéãäžããããã«ããã®äžæåæ¢ãå¿ èŠã§ãã ãããã£ãŠãæšæºã®Meteor.loginWithPasswordã¡ãœãããæŸæ£ããããã¥ã¡ã³ãã«ã¯ãªãMeteor.loginWithTokenã¡ãœããã䜿çšããå¿ èŠããããŸãã ãã®ã¡ãœããã䜿çšãããšããŠãŒã¶ãŒã¯ãMongoDBã«æ¢ã«çæããã³ä¿åãããŠããããŒã¯ã³ã䜿çšããŠã·ã¹ãã ã§èªèšŒã§ããŸãã
è¡åæ¹é
æé ïŒ
- LoginProcedureãåŒã³åºãMeteorã¡ãœããã§èªèšŒããã»ã¹å šäœã眮ãæããŸãã
- æåã®èŠçŽ ãšãã¹ãŠã®çš®é¡ã®ãã§ãã¯ã®æ€èšŒã
- 2çªç®ã®èŠçŽ ïŒã³ãŒãïŒãçæããTwilioã䜿çšããŠéä¿¡ããŸãããã®ã¹ãããã¯ã2çªç®ã®èŠçŽ ãšãã®é ä¿¡ãçæããä»»æã®æ¹æ³ã«çœ®ãæããããšãã§ããŸãã
- ã³ãŒããšãã®ä»ã®ããŒã¿ãå¥åã®MongoDBã³ã¬ã¯ã·ã§ã³ã«ä¿åãããªãŒãã³èªèšŒã»ãã·ã§ã³ãä¿åããŸãã
- ã¯ã©ã€ã¢ã³ãããŠãŒã¶ãŒã«2çªç®ã®èŠçŽ ãå ¥åããããã«èŠæ±ããäžéçµæãè¿ããŸãã
- 2çªç®ã®èŠçŽ ã®åä¿¡ãšç¢ºèªã
- æ°ããããŒã¯ã³ãçæããã¯ã©ã€ã¢ã³ãã«è¿ããŸãã
- ã¯ã©ã€ã¢ã³ãã¯ãåä¿¡ããããŒã¯ã³ã䜿çšããŠ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ã€ã®åçãªãã·ã§ã³ã®ãããããåãåãäºå®ã§ãã
- 400-ã»ãã·ã§ã³ã¯æ€èšŒã«åæ ŒããŸããã§ããïŒttlãæéåãã§ãïŒãã¯ã©ã€ã¢ã³ãã¯ããã·ã¥ãæ¶å»ããå¿ èŠããããŸãã
- 200-æåã®èŠçŽ ãæž¡ããã2çªç®ã®èŠçŽ ããªã³ã«ãªã£ãŠããªããããèªèšŒå¯èœãªããŒã¯ã³ãå°çããŸããã
- 403-æ°ããã³ãŒãïŒ2çªç®ã®èŠçŽ ïŒãçæããã³éä¿¡ãããå ¥åçšã®ã¢ãŒãã«ãããã¢ããã衚瀺ãããŸãã
- 401-å€ãã³ãŒãïŒ2çªç®ã®èŠå ïŒã¯ãŸã ã¢ã¯ãã£ãã§ããæ®ãã®ã»ãã·ã§ã³ã®æå¹æéãšåãã³ãŒããå ¥åããå¿ èŠæ§ã衚瀺ãããããã¢ããã衚瀺ããŸãã
ã¢ãŒãã«ãŠã£ã³ããŠããã clickïŒmodal-code-submitã€ãã³ãã«ãããåãMeteorã¡ãœãããåŒã³åºããŸãããã³ãŒãïŒ2çªç®ã®èŠçŽ ïŒãæž¡ããŸãã ãã®çµæã次ã®2ã€ã®åçã®ãããããåãåãããšãæåŸ ããŠããŸãã
- 400-ã»ãã·ã§ã³ã¯ãã§ã«æå¹æéãåããŠããŸãããšã©ãŒã衚瀺ããlocalstorage / cookieã®ããã·ã¥ãã¯ãªã¢ããŸãã
- 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ã®åçŽãªã¯ãšãªã
次ã«ãã»ãã·ã§ã³ãæ€èšŒããŸãã å°ãªããšãã以äžã確èªããå¿ èŠããããŸãã
- ãã®ã»ãã·ã§ã³ããã§ã«éããããŠãããã©ããã
- ttlã®æå¹æéãåããŠããŸããïŒ
- ã»ãã·ã§ã³ãéããããã®è©Šè¡åæ°ã確èªããŠãã ããã
- ã³ãŒãã¯äžèŽããŸããïŒ2çªç®ã®èŠå ïŒã
- ãŠãŒã¶ãŒãšã®ã»ãã·ã§ã³ã³ã³ãã©ã€ã¢ã³ã¹ã®è¿œå ãã§ãã¯ã
æ€èšŒãæåãããšãã»ãã·ã§ã³ã¯éãããããšèŠãªãããŸãã ãããã£ãŠãã»ãã·ã§ã³çµäºæéãè¿œå ããããšã«ãããMongoDBã®ãšã³ããªãæŽæ°ããŸãã
次ã«ã以åã«å®çŸ©ãããé¢æ°ã䜿çšããŠãŠãŒã¶ãŒã®æ°ããããŒã¯ã³ãçæããã¯ã©ã€ã¢ã³ãåŽã«éãè¿ããŸãã
ã¹ããã8
ãã®ã¹ãããã®ã³ãŒãã¯æåŸã®ã¹ãããã«å«ãŸããŠããŸãã ã¯ã©ã€ã¢ã³ãåŽã§ã¯ãããŒã¯ã³ãåä¿¡ãããšãããã«Meteor.loginWithTokenã¡ãœãããåŒã³åºããŠèªèšŒã«æåããŸãã
ãããã«
å€ãã®APIã«ãšã£ãŠãMeteor.jsã¯ãè€éã§è€éãªæäœãè¡ãããšãã§ããªããããéããããå¶éãããŠããããã«èŠããå ŽåããããŸãã ãã ãããã®èšäºã§èª¬æããããã«ãæšæºããã±ãŒãžã«ã¯åãŸããªãããã«èŠããæ©èœãããæ·±ãèŠãŠå®è£ ã§ããŸãã
ãã¡ãããæãéèŠãªããšã¯ãå ¬åŒããã¥ã¡ã³ãã«èšèŒãããŠããªãé ãé¢æ°ã䜿çšããªããã°ãªããªãã£ãããšã§ãã ãããã®æ©èœã¯èŠåãªãã«å€æŽãããå¯èœæ§ããããããå€ãã®äººãèŠåãåããŸãã ãããããããããªããã°ãTFAãå€ããå°ãªããéåžžã®åœ¢åŒã§å®è£ ããããšã¯å°é£ã§ãã å°ãªããšãå·çæç¹ã§ã¯ãåäžã®å®è£ ãèŠã€ããããšãã§ããŸããã§ããã