Skip to main content
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Eymard Ventura
Ben Nadel at CF Summit West 2024 (Las Vegas) with: Eymard Ventura

Authenticating Twilio Requests Using Basic Authentication, SSL, And ColdFusion

By
Published in

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).

Want to use code from this post? Check out the license.

Reader Comments

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel