Accounts-Phone
Phone and OTP based login for Meteor.
Installation
In a Meteor app directory, enter:
$ meteor npm install --save libphonenumber-js $ meteor add settlin:accounts-phone
I have removed libphonenumber-js
from the dependencies so that the library is not added twice if you also use it in your app. If you don't then anyways it will be loaded once.
The database
In accordance with the current emails
field used by accounts-password
, we use phones
field in the user document: {phones: [{number, verified: false}]}
. The otp is stored in services.phone.otp
.
The flow
Use a simple Meteor method,
1function sendOtpViaSms(otp) {.....} // the function through which you send sms 2 3Meteor.methods({ 4 sendOtpForLogin: function(to) { 5 if (Meteor.isClient) return null; 6 7 // otp must be generated on the server and never revealed to the client 8 check(to, String); 9 10 let user = Meteor.users.findOne({'phones.number': to}); 11 12 // if there is no user with the given phone number, we create a new one. 13 // Accounts.createUser is available only on the server and creates a new user with two fields: `phones` and `services`. It ensures that the phone numbers are always unique for users. 14 if (!user) user = {_id: Accounts.createUserWithPhone({phone: to})}; 15 16 // send otp as sms 17 let otp = Math.round(Random.fraction() * 100000); 18 19 // Accounts.setPhoneOtp sets the otp in the `__otps` collection: {phone, otp, purpose: '__login__'}. 20 Accounts.setPhoneOtp(user._id, otp); 21 }, 22});
Use this method to send otp whenever needed. Next, take the otp from the user and call,
Meteor.loginWithPhone({phone, otp}, callback);
This method works as any other Meteor.loginWith<Service>
method.
Simple API
Server
1Meteor.otps; // the collection that contains otps in the form {phone, otp, purpose, createdAt} with an index created by: Meteor.otps._ensureIndex({phone: 1, purpose: 1}, {unique: true, name: 'phoneAndPurpose'}); 2// not available on client 3 4/** 5 * @summary Set the otp for a user. 6 * @locus Server 7 * @param {String} userId The id of the user to update. 8 * @param {String} otp OTP 9 * @returns {Void} null 10 */ 11Accounts.setPhoneOtp = function(userId, otp) {...}; 12 13/** 14 * @summary Add a phone number for a user. Use this instead of directly 15 * updating the database. The operation will fail if there is a different user 16 * with same phone. 17 * @locus Server 18 * @param {String} userId The ID of the user to update. 19 * @param {String} newPhone A new phone number for the user. 20 * @param {Boolean} [verified] Optional - whether the new phone number should 21 * be marked as verified. Defaults to false. 22 * @returns {Void} null 23 */ 24Accounts.addPhone = function(userId, newPhone, verified) {...}; 25 26/** 27 * @summary Remove an phone number for a user. Use this instead of updating 28 * the database directly. 29 * @locus Server 30 * @param {String} userId The ID of the user to update. 31 * @param {String} phone The phone number to remove. 32 * @returns {Void} null 33 */ 34Accounts.removePhone = function(userId, phone) {...}; 35 36/** 37 * @summary Create a user directly on the server. Unlike the client version, this does not log you in as this user after creation. 38 * @locus Server 39 * @param {Object} options Object with arguments. Needs just {phone: String} for now. 40 * @returns {String} userId The newly created user's _id 41 */ 42Accounts.createUserWithPhone = function(options) {...}; 43 44/** 45 * @summary finds user by doing a phone number search. Throws error if multiple found. 46 * @param {String} phone phone number. 47 * @return {Object} user document 48 */ 49Accounts.findUserByPhone = function(phone) {...};
Client
1/** 2 * @summary Log the user in with a password. 3 * @locus Client 4 * @param {Object} options phone and otp 5 * @param {Function} [callback] Optional callback. Called with no arguments on success, 6 * or with a single `Error` argument on failure. 7 * @return {Void} null 8 */ 9Meteor.loginWithPhone = function(options, callback) {...};
both
1/** 2 * @summary Log the user in with a password. Indian phone numbers are accepted without 91. For others, the country code is required. Uses https://github.com/halt-hammerzeit/libphonenumber-js 3 * @locus Both 4 * @param {String} phone phone number 5 * @return {String} sanitized phone number. Tweaked for Indian numbers, but works for other countries as well. 6 */ 7 8Accounts.sanitizePhone = function(phone) {...};
If you need a phone + password login, use https://github.com/okland/accounts-phone.