Created
March 25, 2014 11:22
Authenticating Twilio Requests Using Basic Authentication, SSL, And ColdFusion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!--- | |
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 ) ) )#" | |
/> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment