Build a passcode protected conference line with Twilio and Ruby

July 07, 2020
Written by
Phil Nash
Twilion
Reviewed by

Build a passcode protected conference line with Twilio and Ruby

We've explored building a conference line with Ruby on Rails before, but the result there was a conference call that anyone could join. If you want to keep your calls a little more private you will want to protect your conference lines with a passcode.

In this post we will take the Rails application we previously developed and add a stage where we ask for a passcode, only allowing callers into the call if they enter it correctly.

What you'll need

In order to build this conference application you will need:

Once you've got all of that, we'll get started.

Getting up and running with Rails

In the last blog post we built a conference application and we'll continue with that code base. If you don't already have the application, download or clone it from GitHub.

git clone https://github.com/philnash/conference-calls-on-rails.git -b open-conference-line
cd conference-calls-on-rails

Install the dependencies:

bundle install

Start the server to make sure everything is working as expected:

bundle exec rails server

While the server is running you can make a POST request to our existing conference call webhook endpoint to see it working. It should look a bit like this:

$ curl --data "" localhost:3000/calls
<?xml version="1.0" encoding="UTF-8"?>
<Response>
  <Say voice="alice">Welcome to the conference call, let's dial you in right away.</Say>
  <Dial>
    <Conference>Thunderdome</Conference>
  </Dial>
</Response>

This response is TwiML and tells Twilio what to do with a call. If you set a phone number's incoming call webhook to the /calls endpoint in this application then anyone who calls will be welcomed and then entered into the conference call.

Protecting a conference line

Instead of directing a caller straight into a conference call, this time we want to greet the caller and ask them for a code. When they enter the code we will check it against a code we set and if they match they will be allowed to join the conference call.

To achieve this we will use <Gather> to receive their input. Let's see how that works.

First, we will need a few new actions, so generate a new controller to contain them.

bundle exec rails generate controller protected_calls

Open app/controllers/protected_calls_controller.rb, we'll add our new actions here.

For this webhook controller we need to skip the cross-site request forgery (CSRF) protection. The controller is not intended to respond to requests from users, it is for incoming webhooks from Twilio, so CSRF protection is unnecessary. Instead, check out this post on how to protect webhook endpoints in Ruby applications by validating the request signature using Rack middleware.

To skip the protection, add this to the top of the controller class:

class ProtectedCallsController < ApplicationController
  skip_before_action :verify_authenticity_token
end

Building our actions

Next, let's add an action that welcomes the user and asks them for the code. The Twilio Ruby gem is already installed in this application, so we can use the TwiML helpers to build up our response.

Call the first action welcome and create a new Twilio::TwiML::VoiceResponse object to build up the response. We are gathering input from the caller, so we'll start using <Gather> and then nest a <Say> within it. TwiML is a subset of XML, so at the end of the action we render the result as XML.

class ProtectedCallsController < ApplicationController
  skip_before_action :verify_authenticity_token

  def welcome
    twiml = Twilio::TwiML::VoiceResponse.new
    twiml.gather(action: protected_calls_verify_path) do |gather|
      gather.say(voice: "alice", message: "Please enter the code to enter the conference followed by the hash.")
    end
    render xml: twiml
  end
end

<Gather> will listen for the user input once the message has been read out. Once the user enters the code and presses the # key Twilio will then make a request to the action attribute that we added. Above I used the URL protected_calls_verify_path which doesn't exist yet. When Twilio makes the request to the action it sends the digits that the user pressed in the Digits parameter of the request.

We need to build another action that will check the digits against a code we decided on before the call and use that to decide whether the caller can join the conference or if they got it wrong, direct them to enter the code again. If they can join the conference we will return TwiML with <Dial> and <Conference> to drop them into the call. Otherwise we will read out another message with <Say> and <Redirect> them back to the welcome action.

For the purposes of this application, we will add the code as an environment variable called CONFERENCE_PIN.

Add the following code to your controller:

class ProtectedCallsController < ApplicationController
  skip_before_action :verify_authenticity_token

  def welcome
    twiml = Twilio::TwiML::VoiceResponse.new
    twiml.gather(action: protected_calls_verify_path) do |gather|
      gather.say(voice: "alice", message: "Please enter the code to enter the conference followed by the hash.")
    end
    render xml: twiml
  end

  def verify
    twiml = Twilio::TwiML::VoiceResponse.new
    digits = params["Digits"]
    if digits == ENV["CONFERENCE_PIN"]
      twiml.say(voice: "alice", message: "Thank you, joining the conference now.")
      twiml.dial do |dial|
        dial.conference("Vault")
      end
    else
      twiml.say(voice: "alice", message: "Sorry, that code is incorrect.")
      twiml.redirect(protected_calls_welcome_path)
    end
    render xml: twiml
  end
end

Routes

We're almost done here, but we haven't sorted our routes out just yet. Open up config/routes.rb and add the following:

Rails.application.routes.draw do
  resources :calls, only: [:create]
  post 'protected_calls/welcome', to: 'protected_calls#welcome'
  post 'protected_calls/verify', to: 'protected_calls#verify'
end

This adds POST routes to the 2 actions in the controller that we just created.

Testing it all out

That's all the code we need, the next thing to do is test it. We need to start up the application with a PIN in the environment, on the command line run:

CONFERENCE_PIN=382302 bundle exec rails server

You can use whatever PIN code you like.

Next, we will need to use ngrok to open a tunnel and give us a public URL to this application. The server should be running on localhost on port 3000, so run the following command in another terminal window:

ngrok http 3000 --host-header "localhost:3000"

This will start ngrok and show you a URL you can use, it should look like https://RANDOM_SUBDOMAIN.ngrok.io. Grab that URL and open up your Twilio console. We need to set our phone number's incoming call webhook to this URL plus the welcome path we just created. The whole URL we will use will be: https://RANDOM_SUBDOMAIN.ngrok.io/protected_calls/welcome.

Find your phone number in the Twilio console, edit it and add the URL as the incoming webhook for voice calls.

Now, dial your phone number. You will be greeted with your welcome message, enter the incorrect code and you will be denied access and asked again. Enter the correct code and you will be dropped into a conference call, waiting for anyone else who knows the code to join you.

What else could you build?

In this post you have seen how to take user input on a phone call in order to secure a conference line. If you want to see more things you can do with conference calls, check out the Conference TwiML docs to learn how to join a Conference on mute, silently bridge calls, or build a moderated conference call.

If you want to check out the complete code for this application you can find it on GitHub. I'd love to hear what you're building with Twilio too, so do hit me up on Twitter at @philnash or drop me an email at philnash@twilio.com.