Phone number verification via Twilio SMS in Meteor

Phone number verification via Twilio SMS in Meteor

lovetap.org, my current side-project, sends people pictures from their Google photo albums directly to their phone at random intervals.
I wanted to make sure that people who sign up for the service actually own the phone number they're signing up with. I found a lot of tutorials on how to send a random code to a user's phone that they then have to enter on the website in order to verify their phone, but I was looking for something even simpler. lovetap will send you a text and all you have to do is reply "Yes" to verify your phone number.
How do you implement something like this with Twilio in a Meteor application? Well, it's actually not that hard:

First, you'll need a twilio account (duh).

When a user enters a number that hasn't been verified, send a text message asking them to verify (this is server-side code):

HTTP.post(twilioBaseUrl + "/Accounts/" + twilioAccountSid + "/Messages", {
  auth: twilioAccountSid + ":" + twilioAuthToken,
  params: {
    From: twilioFromNumber,
    To: toNumber,
    Body: body,
  },
});

Now, this is where it gets a little more complicated. The next step is to mark the number as verified when a user replies "Yes" to the message we just sent him. To achieve this, we'll tell twilio to hit a web hook in our application whenever someone responds to a text message.

In your twilio account, go to "Dev Tools", then "TwiML Apps". Enter a friendly name, and under "SMS & MMS", the URL of the webhook in your application. This is the URL that twilio will get whenever one of your users replies to a text message you sent them. I set it to "GET" as I couldn't get it to work with a POST. In the Meteor app, we'll have to create a new route in iron router for our web hook:

this.route('/webhooks/twilio/handleresponse', {
    where: 'server'
})
.get(function() {
    this.response.end(Twilio.handleResponse(this));
});

Now, we just need to handle the data that twilio sends in the request:

// extract data from twilio-post
var date = new Date();
var messageSid = routeContext.params.query.MessageSid;
var accountSid = routeContext.params.query.AccountSid;
var from = routeContext.params.query.From;
var to = routeContext.params.query.To;
var body = routeContext.params.query.Body;

if (!from) {
  return "No from-number";
}

// unfortunately, iron router loses the + because it's URL encoded.
// we'll clumsily add it by hand:
from = "+" + from.trim();
to = "+" + to.trim();

// validated or not?
var verified = false;
body = body.toLowerCase();
if (body.indexOf("yes") !== -1 || body === "y") {
  verified = true;
}

// update validated numbers collection
var responseBody = "";
if (verified) {
  // set number to verified:
  NumberStatus.update(
    {
      clean_number: from,
    },
    {
      $set: {
        verified_on: date,
      },
    },
    {
      multi: true,
    }
  );

  responseBody = "Thanks! You'll soon receive your first picture!";
} else {
  // set number to not verified:
  NumberStatus.update(
    {
      clean_number: from,
    },
    {
      $unset: {
        verified_on: date,
      },
    },
    {
      multi: true,
    }
  );

  responseBody =
    'OK, we won\'t send you pictures.\nIf you change your mind, simply reply "Yes" to this message.';
}

// Set the headers
routeContext.response.writeHead(200, {
  "Content-Type": "application/xml",
});

// respond with twiml:
return '<?xml version="1.0" encoding="UTF-8"?><Response><Sms from="[TWILIOFROM]" to="[TO]">[BODY]</Sms></Response>'
  .replace("[TWILIOFROM]", twilioFromNumber)
  .replace("[TO]", from)
  .replace("[BODY]", responseBody);

We now marked the number as verified in our database and can safely send to it.