Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Dan Wilson's 2011 (North Carolina) with:

Authenticating Twilio Requests Using Basic Authentication, SSL, And ColdFusion

By Ben Nadel on
Tags: ColdFusion

Yesterday, I demonstrated how to authenticate Twilio responses by verifying the given Twilio signature with HMAC-SHA1 hashing. Then, last night, I got into a Twitter conversation with Rick Osborne and Jason Dean about the merits of using Basic Authentication over an SSL (HTTPS) connection. Basic Authentication is an easy way to pass login credentials along with a URL. The downside to using basic authentication is that it is not terribly secure - it obfuscates the credentials, but passes them out in the open. As Jason pointed out to me, however, if one were to pass basic authentication credentials over an HTTPS request, the credentials would be both obfuscated as well as encrypted. And, since using an SSL connection does not seem to carry the computational overhead that is once did, I thought it was time to explore Twilio authorization using Basic Authentication.

 
 
 
 
 
 
 
 
 
 

Twilio supports Basic (and Digest) authentication for both its Phone and SMS end points. In order to utilize this authentication, you need to pass your username and password as part of your end point url:

 
 
 
 
 
 
Twilio SMS End Point Configured To Use Basic Authentication Over An HTTPS Connection. 
 
 
 

Notice here that I am simply passing my username and password credentials to my end point by inserting "username:password@" before the host name. Also notice that I am using an HTTPS (SSL) connection. Since basic authentication passes your credentials out in the open, it is highly recommended that you do this over an HTTPS connection.

As I have blogged about before, when you use basic authentication, your username and password get Base64-encoded and placed in an "Authorization" header along with the HTTP request:

Basic dHJpY2lhOnN1cGVyc2V4eQ==

The first part of this header value, "Basic," simply denotes that we are using basic authentication. The latter part of the string is the Base64-encoded value containing your username and password:

username:password

When the incoming Twilio request reaches our ColdFusion SMS end point, we have to verify several things:

  • The authorization header exists.
  • The authorization header has the credentials.
  • The credentials are in the right format.
  • The credentials are authorized to access our system.

Any one of these steps might fail validation or might simply cause an unforseen technical error (ex. decoding an invalid Base64 value). As such, in a situation like this, I prefer to use a CFTry/CFCatch/CFThrow approach to work flow management. As I outlined in my Twitter API presentation, using a CFTry/CFCatch/CFThrow work flow allows our code to become substantially more straightforward. By throwing errors at any point of failure, it affords the insight at each step that all prior steps executed without concern.

To demonstrate this work flow in a Twilio context, let's take a look at my sample ColdFusion SMS end point below. You'll notice that the first half of the code is dedicated to request authorization while the latter half of the code is dedicate to responding to the SMS request.

Twilio SMS End Point

  • <!---
  • When we check for authorization, there's a number of things
  • that can go wrong, all of which will indicate that the
  • incoming request is not authorized. As such, let's wrap this in
  • a try/catch such that if any part of it fails, we can respond
  • with a 401 Unauthorized response.
  • --->
  • <cftry>
  •  
  • <!--- Get the HTTP request headers. --->
  • <cfset headers = getHttpRequestData().headers />
  •  
  • <!---
  • Check to see if the authorization header exists. This is
  • the value that contains our obfuscated (base64-encoded)
  • login credentials.
  • --->
  • <cfif !structKeyExists( headers, "Authorization" )>
  •  
  • <!--- Throw an error. --->
  • <cfthrow
  • type="AuthorizationNotProvided"
  • message="You must provide authorization credentials to access this system."
  • />
  •  
  • </cfif>
  •  
  • <!---
  • At this point, we know that the client (Twilio Proxy) has
  • provided authorization headers; now, let's unecode them to
  • compare them to our valid credentials. This will be in the
  • form of:
  •  
  • Basic dHJpY2lhOnN1cGVyc2V4eQ==
  •  
  • We need to get the latter part, which a Base64-encoded string
  • in the form of username:password.
  • --->
  • <cfset encodedCredentials = listLast( headers.authorization, " " ) />
  •  
  • <!--- Convert the encoded credentials into a plain string. --->
  • <cfset credentials = toString( toBinary( encodedCredentials ) ) />
  •  
  • <!---
  • Check to make sure that the credentials conform to a valid
  • authorization string, username:password.
  • --->
  • <cfif !reFind( "^[^:]+:.+$", credentials )>
  •  
  • <!---
  • The client (Twilio Proxy) did not provide a valid
  • credentials string. Throw an error.
  • --->
  • <cfthrow
  • type="MalformedCredentials"
  • message="You must provide your authorization credentials in the form of [username:password]."
  • />
  •  
  • </cfif>
  •  
  • <!---
  • Now that we know the credentials are in the proper format,
  • we can parse out the username and password values and compare
  • them to our internal credentials.
  • --->
  • <cfset username = listFirst( credentials, ":" ) />
  • <cfset password = listLast( credentials, ":" ) />
  •  
  • <!---
  • Compare the username and password to our list of accepted
  • credentials.
  • --->
  • <cfif !(
  • (username eq "joanna") &&
  • (password eq "Slipp3ryWh3nW3t")
  • )>
  •  
  • <!---
  • The username and password are not authorized. Throw
  • an error.
  • --->
  • <cfthrow
  • type="Unauthorized"
  • message="The credentials that you have provided are not authorized to access this system."
  • />
  •  
  • </cfif>
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!---
  • If we have made it this far, then the user has provided the
  • properly formed, authenticated, and authorized credentials.
  • Allow them to continue on with the page request.
  • --->
  •  
  •  
  • <!--- ------------------------------------------------- --->
  • <!--- ------------------------------------------------- --->
  •  
  •  
  • <!---
  • For the most part, we are going to generically catch any
  • errors. However, if the error is that the provided
  • credentials simply aren't valid, then return a standard
  • XML response WITHOUT the 401 Unauthorized response. Even if
  • we provide a TwiML response with our 401 status code, Twilio
  • will NOT pass the response back to the user (because it
  • thinks it's still trying to authenticate).
  •  
  • NOTE: I would NOT *really* do this in a live system, this is
  • just for demo and exploration purposes. After all, if the
  • SMS end point is NOT authorized, then something is clearly
  • NOT configured properly in your Twilio phone number.
  • --->
  • <cfcatch type="Unauthorized">
  •  
  • <!--- Create an access denied TwiML response. --->
  • <cfsavecontent variable="responseXML">
  •  
  • <?xml version="1.0" encoding="UTF-8"?>
  • <Response>
  • <Sms>Access Denied</Sms>
  • </Response>
  •  
  • </cfsavecontent>
  •  
  • <!--- Return the access denied SMS response. --->
  • <cfcontent
  • type="text/xml"
  • variable="#toBinary( toBase64( trim( responseXML ) ) )#"
  • />
  •  
  • </cfcatch>
  •  
  • <!---
  • Catch any error that is NOT part of the specific username and
  • password authentication. Any error here will indicate that the
  • request is not authorized.
  • --->
  • <cfcatch>
  •  
  • <!--- Send back an unauthorized status code. --->
  • <cfheader
  • statuscode="401"
  • statustext="Unauthorized"
  • />
  •  
  • <!---
  • Alert the client that we support basic authentication in
  • the realm of SMS end points. Without this, the client
  • will not know how to authenticate itself.
  • --->
  • <cfheader
  • name="WWW-Authenticate"
  • value="basic realm=""SMS"""
  • />
  •  
  • <!--- Return the access denied body. --->
  • <cfcontent
  • type="text/plain"
  • variable="#toBinary( toBase64( 'Access Denied' ) )#"
  • />
  •  
  • </cfcatch>
  •  
  • </cftry>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • If we have made it this far, then we know this user is
  • authorized. Let's param the form values and proceeed with
  • standard usage.
  • --->
  • <cfparam name="form.from" type="string" default="" />
  • <cfparam name="form.body" type="string" default="" />
  •  
  • <!---
  • Build the response. For our demo purposes, just echo back the
  • passed-in SMS text message.
  • --->
  • <cfset response = "Thanks for the message: #form.body#" />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!--- Convert the message into Twilio XML response. --->
  • <cfsavecontent variable="responseXml">
  • <cfoutput>
  •  
  • <?xml version="1.0" encoding="UTF-8"?>
  • <Response>
  • <Sms>#xmlFormat( response )#</Sms>
  • </Response>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Stream XML response to Twilio client. Make sure to TRIM
  • the XML response such that it is valid XML.
  • --->
  • <cfcontent
  • type="text/xml"
  • variable="#toBinary( toBase64( trim( responseXml ) ) )#"
  • />

In the authentication and authorization portion of the code, you can see that I am making good use of the CFThrow tag; any time that I find a situation in which the request fails authorization, I raise the appropriate exception. And, while I am using different Type values for my various errors, ideally, I only need one CFCatch tag. After all, if you fail authorization, you fail authorization. For this demo, however, I did add a superfluous CFCatch tag to handle "Unauthorized" errors specifically. If you return a "401 Unauthorized" HTTP status code, Twilio will not pass the response back to the mobile device even if a valid TwiML (Twilio Markup Language) response is provided. As such, for experimentation only, I am providing a "200 OK" status response in the specific case that the provided username and password is not authorized. However, since the username and password values are hard-coded in our SMS end point URL, this is pretty much an invalid use case.

Sending an SMS text message to the above Twilio end point will result in basic authorization and an SMS text message response:

 
 
 
 
 
 
SMS Response From A Twilio SMS End Point That Is Using Basic Authentication Over HTTPS. 
 
 
 

The code we need to handle basic authentication is not small. It is, however, much more straightforward than using HMAC-SHA1 hashing to authenticate a Twilio request. If you have an SSL certificate for your web application, I would strongly suggest using this approach over using the signature-based approach. The other benefit to using basic authentication is that it makes it much easier to test your SMS end point directly within a browser (without having to use the Twilio Proxy).




Reader Comments

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.