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
libphonenumber-js
is used by Accounts.sanitizePhone()
. I do not include it as a dependency so that if one overwrites the sanitizePhone function, the library is not added in vain.
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
.
You will need to create an index on the collection, to ensure performance
Meteor.otps.rawCollection().createIndex({phone: 1, purpose: 1}, {unique: true, name: 'phoneAndPurpose'});
The flow
Use a simple Meteor method,
1function sendOtpViaSms(otp) {.....} // the function through which you send sms 2 3Meteor.methods({ 4 // to 5 sendOtpForLogin: function(toPhone) { 6 if (Meteor.isClient) return null; 7 8 // otp must be generated on the server and never revealed to the client 9 check(toPhone, String); 10 11 // send otp as sms 12 let otp = Math.round(Random.fraction() * 100000); 13 14 // Accounts.setPhoneOtp sets the otp in the `__otps` collection: {phone, otp, purpose: '__login__'}. 15 Accounts.setPhoneOtp(toPhone, otp); 16 }, 17});
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 finds user by doing a phone number search. Throws error if multiple found. 38 * @param {String} phone phone number. 39 * @return {Object} user document 40 */ 41Accounts.findUserByPhone = function(phone) {...}; 42 43/** 44 * @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 45 * @locus Server 46 * @param {String} phone phone number 47 * @return {String} sanitized phone number. Tweaked for Indian numbers, but works for other countries as well. 48 */ 49Accounts.sanitizePhone = function(phone) {...};
Client
1/** 2 * @summary Log the user in with a password. 3 * @locus Client 4 * @param {Object} options phone, otp and optional expectedUserId 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({ 10 phone, 11 otp, 12 expectedUserId // optional. the user id which is expected for this phone. It is needed because phone numbers may not be unique, and at times we may need to check if the login is for an expected old user or a new user. 13}, callback) {...}; 14 15/** 16 * @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, as dynamic import 17 * @locus Server 18 * @param {String} phone phone number 19 * @return {String} sanitized phone number. Tweaked for Indian numbers, but works for other countries as well. 20 */ 21Accounts.sanitizePhone = async function(phone) {...};
If you need a phone + password login, use https://github.com/okland/accounts-phone.