I need a SMS-authentication in my Meteor app.
Let's say I have a simple form (in React-style, because I'm using React in frontend):
<form onSubmit={ this.submitPhone() }>
<input type='text' size='10' placeholder='Your phone here' />
<input type='submit' value='Send me a code'/>
</form>
User enters his phone number and submits the form. After that SMS-code is sent to the entered number. And a new form appears:
<form onSubmit={ this.submitCode() }>
<input type='text' size='5' placeholder='Enter code' />
<input type='submit' value='Sign In'/>
</form>
If user enters his code correctly, then Meteor should know that the user is logged in (with some _id, I think). If the code is not correct, then an error message is shown.
I found Twilio service and this package and it looks like it is exactly what I need. But I don't know how to use it at all.
I have tried only Meteor's default Accounts UI way of authentication a few months ago in the tutorials, but actually I don't know how to do such things, especially via SMS. I don't need such things like roles in my app, I even don't need usernames, passwords and e-mails. I just need to have a base of user _id
and phone
. So all I need is make user be able to sign in (first time signin is signup in this way).
Thank for your help, a detailed answer is really what I need this time.
First, you need to install one of the following packages:
Next, you should also install the okland:accounts-phone package to help enable login via phone number. Their GitHub provides easy to follow instructions on how to set it up.
Password
I would strongly recommend creating user accounts with a password, along with the phone number, since it is a good security feature to have, and is also required by default on Meteor Accounts package.
Verification Process
I will be giving an example using server-side Meteor methods, for frontend you can write your React handlers accordingly.
This example will be using the HTTP package, in your code you can modify it to include other wrapper packages like twilio-meteor if you wish.
Step 1: Register your user and send verification SMS.
createNewUser method:
'createNewUser': function (password, phoneNumber) {
var min = 10000;
var max = 99999;
var random = Math.floor(Math.random() * (max - min + 1)) + min;
var verified = Meteor.users.find({username: phoneNumber}).fetch();
if (verified.length > 0) {
if (verified.length == 1 && verified[0].profile.isMobileVerified == 'NO') {
Meteor.users.remove({username: phoneNumber});
var user = {username: phoneNumber, password: password, profile: { randomSms: random, isMobileVerified: 'NO' }};
Meteor.call("sendSMS", random, phoneNumber);
Accounts.createUser(user);
return returnSuccess('Successfully created', phoneNumber);
} else {
return returnFaliure('Mobile number already exists', phoneNumber);
}
} else {
var user = {username: phoneNumber, password: password, profile: { randomSms: random, isMobileVerified: 'NO' }};
Meteor.call("sendSMS", random, phoneNumber);
Accounts.createUser(user);
return returnSuccess('Successfully created', phoneNumber);
}
},
sendSMS method:
'sendSMS': function (code, mobile) {
console.log(mobile);
HTTP.call(
"POST",
'https://api.twilio.com/{yyyy-dd-mm}/Accounts/' +
'{TWILIO_APPKEY}' + '/SMS/Messages.json', {
params: {
From: '+11234567890',
To: mobile,
Body: "Greetings! Your OTP is " + code
},
auth: '{TWILIO_APPKEY}' + ':' + '{TWILIO_PASSWORD}'
},
// Print error or success to console
function (error) {
if (error) {
console.log(error);
}
else {
console.log('SMS sent successfully.');
}
}
);
}
Step 2: Ask user for verification code and check code input by user
verifySMS method:
'verifySMS': function (code, userid) {
console.log(userid);
var sms = Meteor.users.findOne({username: userid}).profile.randomSms;
if (sms == code) {
Meteor.users.update({username: userid}, {
$set: {"profile.isMobileVerified": "YES", "profile.randomSms": "--"}
});
return returnSuccess("Yes");
} else {
return returnSuccess("No");
}
},
Step 3: From your React code handling, if code matches, approve the user, else display appropriate error message.
UPDATE to handle specific use case by OP: (Example indicative of React code)
To have the user authenticated via SMS OTP code everytime before login, you will need to use the sendSMS method every time the user tries to login, update it in a collection of stored AuthCodes, verify the code each time, and handle case accordingly.
React Form: You will need to render a form something like this inside your react JSX code container.
<form className="new-task" onSubmit={this.handleSubmit.bind(this)} >
<input
type="text"
ref="phoneNumberInput"
placeholder="Enter Phone Number"
/>
</form>
Write React function to login user:
handleSubmit() {
event.preventDefault();
// Find the phone number field via the React ref
const phoneNumber = ReactDOM.findDOMNode(this.refs.phoneNumberInput).value.trim();
Meteor.call('sendAuthCode', Meteor.userId(), phoneNumber, function(error, result) {
// Show a popup to user that code has been sent
});
}
Then, similar as above, create another form to have the user input the code sent to them, and send that to server for verification, e.g.
handleAuthCheck() {
event.preventDefault();
// Find the phone number field via the React ref
const phoneNumber = ReactDOM.findDOMNode(this.refs.phoneNumberInput).value.trim();
const code = ReactDOM.findDOMNode(this.refs.codeInput).value.trim();
Meteor.call('verifyAuthCode', Meteor.userId(), phoneNumber, code, function(error, result) {
// handle result accordingly
// you need to decide how you are going to login user
// you can create a custom module for that if you need to
});
}
AuthCodes Collection: You will need to define a collection in a file and export it, so that it can be imported where needed.
export const AuthCodes = new Mongo.Collection('authcodes');
Meteor server methods:
Send SMS:
'sendAuthCode': function(userId, phoneNumber) {
var min = 10000;
var max = 99999;
var code = Math.floor(Math.random() * (max - min + 1)) + min;
Meteor.call("sendSMS", code, phoneNumber);
AuthCodes.insert({
userId: userId,
phoneNumber: phoneNumber,
code: code
});
}
Verify Code:
'verifyAuthCode': function(userId, phoneNumber, code) {
var authCode = AuthCodes.findOne({ phoneNumber: phoneNumber, code: code }) // You can also add userId check for added verification
if(typeof authCode !== "undefined" && authCode) {
// verification passed
return true;
} else {
// verification failed
return false;
}
}