Enabling your users to authenticate to your website using a popular social media service such as Facebook or Twitter can increase your sign-up conversion rates. Having too many choices has been proven to be reduce conversion rates, as it provides your users with too many options and can lead to complexity or confusion. However, having one or two social media sign up links on your sign up page is a good thing.
So, how do we add a Sign In with Twitter button to our website? It’s not that hard. We will use the OAuth 1.0 package that I have shared on GitHub to make it easier. The OAuth authentication follows a three-step authentication process:
- Obtain request token for application.
- Redirect user to Twitter authentication page with the providing the request token. Twitter handles the authentication and application approval steps and then sends the user back to our website after they have successfully signed in.
- Using the request token and another piece of data (oauth_verifier) we can obtain the access token that can be used for subsequent API requests.
Create Twitter Application
The prerequisite for this tutorial is that you have a Twitter application created and configured correctly, and that you have obtained the Consumer Key and Consumer Secret values.
I already have a Twitter Application
If you already have a Twitter application created, there are two possible issues that you should be aware of.
- You must enter the Callback URL. It is just a placeholder, so it doesn’t really matter what you enter here. But, you must enter something, and not “localhost”.
- You must check the Allow this application to be used to Sign in with Twitter checkbox.
- You must copy and paste the Consumer Key and Consumer Secret values from the API Keys page into the example files located in the examples folder: twitter.cfm, twitter_callback.cfm and twitter_verify_credentials.cfm.
I have not created a Twitter Application
If you have not already created a Twitter Application you will need to create one before continuing. Here are the steps that are necessary to create and configure your new application.
- Sign into the Twitter Developer website.
- Go to Application Management and then click on Create New App.
- Note, you must provide a Callback URL. Don’t worry about getting it right, it’s just a placeholder. We will customize our request to specify the callback URL. Further, it will not let you enter localhost, just enter something that is a valid domain. The callback URL provided here is pretty meaningless but is required.
- After your application is created, copy and paste the Consumer Key and Consumer Secret values from the API Keys page into the example files located in the examples folder: twitter.cfm, twitter_callback.cfm and twitter_verify_credentials.cfm.
- Next, go to the Settings for your application and check the Allow this application to be used to Sign in with Twitter checkbox, and click on Update settings.
- Finally, reload this page and click on the Sign In with Twitter button.
Obtain Request Token
Now that we have everything setup, let’s walk through the necessary code to setup our request and obtain the request token from the Twitter API.
<cfsilent>
<cfset CONSUMER_KEY = "">
<cfset CONSUMER_SECRET = "">
<cfset CALLBACK = "http://localhost/OAuth/examples/twitter_callback.cfm">
</cfsilent>
In the code above we have declared three constants to hold the information necessary for our OAuth request.
These values should be customized based on your Twitter application API keys and your local environment setup.
The CALLBACK
constant is the URL that will receive the user authentication information after the user has logged into Twitter.
<cfset oAuthRequest = new com.brianflove.oauth.Request()>
<cfset oAuthConsumer = new com.brianflove.oauth.Consumer()>
<cfset oAuthToken = new com.brianflove.oauth.Token()>
<!---setup consumer--->
<cfset oauthConsumer.setSecret(CONSUMER_SECRET)>
<cfset oauthConsumer.setKey(CONSUMER_KEY)>
<!---setup request--->
<cfset oAuthRequest.setMethod("POST")>
<cfset oAuthRequest.setUrl("https://api.twitter.com/oauth/request_token")>
<cfset oAuthRequest.setCallback(CALLBACK)>
<cfset oAuthRequest.setConsumer(oAuthConsumer)>
<cfset oAuthRequest.setToken(oAuthToken)>
Next, we create the OAuth request object and configure it to make a POST request to the /oauth/request_token service. I am not diving too much into the details of this code, as I explained this in my post on using the OAuth 1.0 project that I created.
<!---use HMAC-SHA1 signature method--->
<cfset signatureMethod = new com.brianflove.oauth.methods.HmacSha1SignatureMethod()>
<!---sign request--->
<cfset oAuthRequest.signWithSignatureMethod(signatureMethod=signatureMethod)>
Next, we sign the request using the HMAC-SHA1 signing method. We are now ready to make our HTTP request to the Twitter API.
<cfset httpRequest = new Http()>
<cfset httpRequest.setUrl(oAuthRequest.toUrl())>
<cfset httpRequest.setMethod(oAuthRequest.getMethod())>
<cfset httpRequest.addParam(type="header", name="Authorization", value=oAuthRequest.toHeader())>
<cfset httpRequest.setCharset("utf-8")>
<cfset httpResult = httpRequest.send().getPrefix()>
In setting up the HTTP request we need to ensure that we use the same HTTP method to match our Request
configuration.
As such, we pull the method and url parameter values back out of the Request
object.
Then we need to add the Authorization header to the request.
The Authorization header is easily obtained from the Request.toHeader()
method.
<!---Verify status code--->
<cfif httpResult.responseHeader.status_code neq 200>
<p>There was an error. The status code indicates that there was an error obtaining the request token.</p>
<cfabort>
</cfif>
<!---Verify result--->
<cfif not Len(httpResult.fileContent)>
<p>There was an error. No response content was returned.</p>
<cfabort>
</cfif>
After making our HTTP request we should verify that the result was successful. The status code of the response should be 200, and we should have received a response body with the oauth_token value. Now, let’s go ahead and parse the response into a struct that we can use for storing the token value.
<!---parse result--->
<cfset parameters = {}>
<cfset pairs = ListToArray(httpResult.fileContent, "&")>
<cfloop array="#pairs#" index="pair">
<cfset key = ListGetAt(pair, 1, "=")>
<cfset value = ListGetAt(pair, 2, "=")>
<cfset parameters[key] = value>
</cfloop>
After parsing the response, again we should verify that everything is correct before storing the token value in our user’s session.
<!---Verify oauth_token--->
<cfif not StructKeyExists(parameters, "oauth_token")>
<p>There was an error. Token was not returned.</p>
<cfabort>
</cfif>
<!---Verify callback_confirmed--->
<cfif not StructKeyExists(parameters, "oauth_callback_confirmed") OR (StructKeyExists(parameters, "oauth_callback_confirmed") AND not IsBoolean(parameters.oauth_callback_confirmed)) OR (StructKeyExists(parameters, "oauth_callback_confirmed") AND IsBoolean(parameters.oauth_callback_confirmed) AND not parameters.oauth_callback_confirmed)>
<p>There was an error. The callback was not confirmed.</p>
<cfabort>
</cfif>
<!---Store request token key and secret--->
<cfset oAuthToken.setKey(parameters.oauth_token)>
<cfif StructKeyExists(parameters, "oauth_token_secret")>
<cfset oAuthToken.setSecret(parameters.oauth_token_secret)>
</cfif>
<!---Store Token instance for the user's session--->
<cfset session.token = oAuthToken>
Lastly, the user is redirected to the Twitter /oauth/authenticate page.
Twitter handles the user authentication and application approval process on their site.
After successfully authenticating and approving our application the user will be redirected back to our website to the URL that we set in our CALLBACK
constant.
<cflocation url="https://api.twitter.com/oauth/authenticate?oauth_token=#parameters.oauth_token#" statuscode="302" addtoken="false">
Process Callback
When the user is redirected back to our callback script, we should first verify the request to ensure it is a valid request from our user.
I am going first verify that I have the token stored in the user’s session.
Next, I will verify that the oauth_token
URL parameter was passed back to the callback.
Then we verify that the token provided as a URL parameter matches what I have stored in the user’s session.
Last, we verify that the oauth_verifier
URL parameter was also provided.
<!---Verify we have the token object in the user's session--->
<cfif not StructKeyExists(session, "token")>
<p>There was an error. Token object not found in session scope.</p>
<cfabort>
</cfif>
<!---Verify that the oauth_token url parameter was passed back to the callback--->
<cfif not StructKeyExists(url, "oauth_token") OR (StructKeyExists(url, "oauth_token") AND not Len(url.oauth_token))>
<p>There was an error. Authentication token not returned.</p>
<cfabort>
</cfif>
<!---Verify that the token we have and the token provided are the same--->
<cfif session.token.getKey() neq url.oauth_token>
<p>There was an error. Authentication token and session token do not match.</p>
<cfabort>
</cfif>
<!---Verify that the oauth_verifier parameter was passed back to the callback--->
<cfif not StructKeyExists(url, "oauth_verifier") OR (StructKeyExists(url, "oauth_verifier") AND not Len(url.oauth_verifier))>
<p>There was an error. Authentication verifier not returned.</p>
<cfabort>
</cfif>
Obtain Access Token
After the verifications have all passed in our callback code we want to use the oauth_verifier
string to obtain the access token for the user.
To do that, we will create another OAuth Request instance to send a POST HTTP request to the oauth/access_token
service.
<!---Perform three-step oAuth authentication to retreive the access token--->
<cfset oAuthRequest = new com.brianflove.oauth.Request()>
<cfset oAuthConsumer = new com.brianflove.oauth.Consumer()>
<!---setup consumer--->
<cfset oauthConsumer.setSecret(CONSUMER_SECRET)>
<cfset oauthConsumer.setKey(CONSUMER_KEY)>
<!---setup request--->
<cfset oAuthRequest.setMethod("POST")>
<cfset oAuthRequest.setUrl("https://api.twitter.com/oauth/access_token")>
<cfset oAuthRequest.setConsumer(oAuthConsumer)>
<cfset oAuthRequest.setToken(session.token)>
<!---Add oauth_verifier parameter to the request--->
<cfset oAuthRequest.addParameter(key="oauth_verifier", value=url.oauth_verifier)>
<!---use HMAC-SHA1 signature method--->
<cfset signatureMethod = new com.brianflove.oauth.methods.HmacSha1SignatureMethod()>
<!---sign request--->
<cfset oAuthRequest.signWithSignatureMethod(signatureMethod=signatureMethod)>
Note that I have added the additional oauth_verifier parameter to the request using the value provided to the callback script. Again, we have signed the request using the HMAC-SHA1 signature method. Now, let’s go ahead and send the request.
<!---POST using request URL--->
<cfset httpRequest = new Http()>
<cfset httpRequest.setUrl(oAuthRequest.toUrl())>
<cfset httpRequest.setMethod(oAuthRequest.getMethod())>
<cfset httpRequest.addParam(type="header", name="Authorization", value=oAuthRequest.toHeader())>
<cfset httpRequest.addParam(type="header", name="Content-Type", value="application/x-www-form-urlencoded")>
<cfset httpRequest.addParam(type="body", value="oauth_verifier=#url.oauth_verifier#")>
<cfset httpRequest.setCharset("utf-8")>
<cfset httpResult = httpRequest.send().getPrefix()>
In the code above I am setting the Content-Type
header value to match a form post as required.
I am also including the oauth_verifier
parameter as the body of the request.
Next, we verify the request response and parse the values returned.
<!---Verify status code--->
<cfif httpResult.responseHeader.status_code neq 200>
<p>There was an error. The status code indicates that there was an error obtaining the request token.</p>
<cfabort>
</cfif>
<!---Verify result--->
<cfif not Len(httpResult.fileContent)>
<p>There was an error. No response content was returned.</p>
<cfabort>
</cfif>
<!---parse result--->
<cfset parameters = {}>
<cfset pairs = ListToArray(httpResult.fileContent, "&")>
<cfloop array="#pairs#" index="pair">
<cfset key = ListGetAt(pair, 1, "=")>
<cfset value = ListGetAt(pair, 2, "=")>
<cfset parameters[key] = value>
</cfloop>
After parsing out the values verify that the oauth_token
and oauth_token_secret
values were provided.
All that is left to do is to store these values into our user’s session token object so that we can use them in subsequent requests to the Twitter API.
<!---Verify oauth_token--->
<cfif not StructKeyExists(parameters, "oauth_token")>
<p>There was an error. Token key was not returned.</p>
<cfabort>
</cfif>
<!---Verify oauth_token_secret--->
<cfif not StructKeyExists(parameters, "oauth_token_secret")>
<p>There was an error. Token secret was not returned.</p>
<cfabort>
</cfif>
<!---Store access token key and secret--->
<cfset session.token.setKey(parameters.oauth_token)>
<cfset session.token.setSecret(parameters.oauth_token_secret)>
Verify User Credentials
The user is now authenticated using Twitter and we are ready to show them our application’s UI. However, before calling things complete, we probably want to obtain some information about our new user, such as their name and Twitter username.
To verify the user credentials we will make a GET request to the /1.1/account/verify_credentials.json service. This service will return the data using JSON, which we can easily consume. This final step assumes that you have completed the previous steps and have the token object stored in the user’s session.
First, we set up our Request
instance and sign the request. This should start to look very familiar now. :)
<cfset oAuthRequest = new com.brianflove.oauth.Request()>
<cfset oAuthConsumer = new com.brianflove.oauth.Consumer()>
<cfset oAuthToken = new com.brianflove.oauth.Token()>
<!---setup consumer--->
<cfset oauthConsumer.setSecret(CONSUMER_SECRET)>
<cfset oauthConsumer.setKey(CONSUMER_KEY)>
<!---setup request--->
<cfset oAuthRequest.setMethod("GET")>
<cfset oAuthRequest.setUrl("https://api.twitter.com/1.1/account/verify_credentials.json")>
<cfset oAuthRequest.setConsumer(oAuthConsumer)>
<cfset oAuthRequest.setToken(session.token)>
<!---use HMAC-SHA1 signature method--->
<cfset signatureMethod = new com.brianflove.oauth.methods.HmacSha1SignatureMethod()>
<!---sign request--->
<cfset oAuthRequest.signWithSignatureMethod(signatureMethod=signatureMethod)>
Next, we send the HTTP request to the Twitter API and parse the JSON response.
<!---POST using request URL--->
<cfset httpRequest = new Http()>
<cfset httpRequest.setUrl(oAuthRequest.toUrl())>
<cfset httpRequest.setMethod(oAuthRequest.getMethod())>
<cfset httpRequest.addParam(type="header", name="Authorization", value=oAuthRequest.toHeader())>
<cfset httpRequest.setCharset("utf-8")>
<cfset httpResult = httpRequest.send().getPrefix()>
<!---Parse JSON response--->
<cfset credentials = DeserializeJson(httpResult.fileContent)>
Now that we have the data, it’s up to you what to do with it. Most likely you will want to persist this data to your database. For now, I am just going to output some of the values that are provided by Twitter.
<cfoutput>
<h1>Hello #credentials.name#</h1>
<p>Here is some data about you.</p>
<ul>
<li>Your Twitter id: #credentials.id#</li>
<li>Your Twitter username: #credentials.screen_name#</li>
<li>You have #credentials.followers_count# followers</li>
<li>You have tweeted #credentials.statuses_count# times</li>
</ul>
</cfoutput>
Download
You can read more about using my OAuth 1.0 package with ColdFusion, which might explain some more of this code in greater detail. The complete source code for this tutorial is available in the examples folder of the project download.
Don’t forget to configure your application and paste in the Consumer_Key and Consumer_Secret strings.