One of the questions I get asked frequently in regards to the monkehTweet ColdFusion Twitter wrapper is the ability to manage multiple users and their access to Twitter.
In this post, I've written a sample application to emulate a user-based system. Once a user has logged in, the application can access this user's Twitter details and send a post to their stream, updating their status.
Check out the video using the above link (or directly on YouTube) to see it in action. Full code with clear comments is included below, and you can download the entire sample application to enjoy and adapt as you wish to suit your needs.
Multiple User Demo
When dealing with Twitter OAuth calls (especially accessing the restricted data requiring authentication), the user's token and token secret are required to form the base request and the signature required to cryptographically sign the request.
monkehTweet requires the consumer application's token and secret values to be sent through in the init() constructor method as the consumer application MUST have these in place to connect to the service provider (in this case Twitter) to be recognised.
However, to communicate on behalf of a specific user, monkehTweet needs to have the user's token and secret value, which are obtained after successful authentication from the user. These values are required to successfully form the authenticated header, base string and signature needed to send to Twitter in the request.
The main function to note in the example and code is the use of setFinalAccessDetails() method within the monkehTweet component. This function will set the OAuth token, OAuth secret and user account name, assuming you have already obtained them from a method of prior authentication.
login.cfm
<!---
Name: login.cfm
Author: Matt Gifford AKA coldfumonkeh
(http://www.mattgifford.co.uk)
Date: 28.01.2011
Copyright 2011 Matt Gifford AKA coldfumonkeh.
All rights reserved.
--->
<cfif structKeyExists (form,'loginBtn')>
<cfquery name="rstUserLogin"
datasource="#application.datasource#">
SELECT
ID,
username,
password
FROM
tblUsers
WHERE
username = <cfqueryparam
cfsqltype="cf_sql_varchar"
value="#form.username#" />
AND password = <cfqueryparam
cfsqltype="cf_sql_varchar"
value="#form.password#" />
</cfquery>
<cfif rstUserLogin.recordcount>
<!---
Query found a user, so let's set the
session values and store the user's previously
saved twitter authentication details.
--->
<cfset session['isLoggedIn'] = true />
<cfset session['userID'] = rstUserLogin.ID />
<!---
Relocate the user back to the index main page.
--->
<cflocation url="index.cfm" addtoken="false" />
</cfif>
</cfif>
<form name="loginForm" method="post">
<label for="username">Username: </label>
<input type="text"
name="username"
id="username" /><br />
<label for="password">Password: </label>
<input type="password"
name="password"
id="password" /><br />
<input type="submit"
name="loginBtn"
value="Log in" />
</form>
The login form is purely to access our internal application and to authenticate the user against our own database using non-Twitter account details. If the user exists, their userID value is set in the SESSION scope (required for a database query in the next screen) and they are transferred to the index.cfm page to proceed.
index.cfm
<!---
Name: index.cfm
Author: Matt Gifford AKA coldfumonkeh
(http://www.mattgifford.co.uk)
Date: 28.01.2011
Copyright 2011 Matt Gifford AKA coldfumonkeh.
All rights reserved.
--->
<cfoutput>
<!---
Only proceed if the user has successfully
logged in to our site.
--->
<cfif session.isLoggedIn>
<!---
Use the stored userID after login to
check if this user has previously authenticated
with us and we have their twitter details.
--->
<cfif structKeyExists (session,'userID')>
<cfquery name="rstTweetDetails"
datasource="#application.datasource#">
SELECT
ID,
userID,
accessToken,
accessSecret,
screen_name,
twitter_userID
FROM
tblTweetAccess
WHERE
userID = <cfqueryparam
cfsqltype="cf_sql_numeric"
value="#session.userID#" />
</cfquery>
<cfif rstTweetDetails.recordcount>
<!---
Sweet! It looks as though we have this user's
authentication access details from a previous visit.
They don't need to authenticate through Twitter anymore.
Store their access details into the session scope.
--->
<cfscript>
session['accessToken'] =
rstTweetDetails.accessToken;
session['accessSecret'] =
rstTweetDetails.accessSecret;
session['screen_name'] =
rstTweetDetails.screen_name;
WriteOutput('<p>You have already
authenticated with Twitter.</p>');
</cfscript>
<!---
Output something to the user
so that they can proceed.
--->
<a href="post.cfm">Send a post using monkehTweets
and see the CFC in action</a>
<cfelse>
<!---
They have logged in, but have not authenticated
through Twitter. We need to send them through
the OAuth validation process.
Firstly we need to have the user grant access
to our application. We do this (using OAuth)
through the getAuthorisation() method.
The callbackURL is optional. If not sent through,
Twitter will use the callback URL it has
stored for your application.
--->
<cfset authStruct =
application.objMonkehTweet.getAuthorisation(
callbackURL='http://[yourdomain]/authorize.cfm'
) />
<cfif authStruct.success>
<!---
Here, the returned information is being
set into the session scope.
You could also store these into a DB
(if running an application for multiple users).
--->
<cfset session['oAuthToken']
= authStruct.token />
<cfset session['oAuthTokenSecret']
= authStruct.token_secret />
</cfif>
<!---
Now, we need to relocate the user to
Twitter to perform the authorisation for us.
--->
<p>To continue, please authenticate with Twitter.</p>
<a href="#authStruct.authURL#">
Authenticate to proceed</a>
</cfif>
</cfif>
</cfif>
</cfoutput>
Once the user has logged in, we can then use the stored userID from the SESSION scope to query against the tblTweetAccess table to see if they have already authenticated with Twitter and we have their token details.
If we do have these details, we set the access token, access secret and their Twitter screen name into the session scope, as they will be used in the post.cfm page to set the authentication details using the setFinalAccessDetails() method.
If we do not have these details, the user needs to be sent to Twitter to start the authentication process. They will be transferred to authorize.cfm after they have actioned the authentication (hopefully approving our application and allowing us access to their Twitter stream).
A successful authentication will send an oauth_verifier parameter in the URL query string.
authorize.cfm
<!---
Name: authorize.cfm
Author: Matt Gifford AKA coldfumonkeh
(http://www.mattgifford.co.uk)
Date: 28.01.2011
Copyright 2011 Matt Gifford AKA coldfumonkeh.
All rights reserved.
--->
<!--- Proceed if the user has approved the application. --->
<cfif structKeyExists(URL, 'oauth_verifier')>
<cfscript>
returnData = application.objMonkehTweet.getAccessToken(
requestToken = session.oAuthToken,
requestSecret = session.oAuthTokenSecret,
verifier = url.oauth_verifier
);
if (returnData.success) {
/*
Save these off to your database against
your user so you can access their
account in the future.
*/
session['accessToken'] = returnData.token;
session['accessSecret'] = returnData.token_secret;
session['screen_name'] = returnData.screen_name;
session['user_id'] = returnData.user_id;
/*
Insert the details for this user into
the database so that we can store the
token details for next time.
*/
queryService = new query();
queryService.setDatasource(application.datasource);
queryService.setName("insertAuthentication");
queryService.addParam( name="userID",
value="#session['userID']#",
cfsqltype="NUMERIC");
queryService.addParam( name="accessToken",
value="#returnData.token#",
cfsqltype="VARCHAR");
queryService.addParam( name="accessSecret",
value="#returnData.token_secret#",
cfsqltype="VARCHAR");
queryService.addParam( name="screen_name",
value="#returnData.screen_name#",
cfsqltype="VARCHAR");
queryService.addParam( name="twitter_userID",
value="#returnData.user_id#",
cfsqltype="NUMERIC");
queryService.setSQL(
"INSERT INTO
tblTweetAccess
(
userID,
accessToken,
accessSecret,
screen_name,
twitter_userID
) VALUES (
:userID,
:accessToken,
:accessSecret,
:screen_name,
:twitter_userID
)"
);
result = queryService.execute();
writeDump(returnData);
}
</cfscript>
<a href="post.cfm">Send a post using monkehTweets
and see the CFC in action</a>
<cfelse>
<p>You denied access for the application.</p>
</cfif>
Following a successful authentication, the user is sent back to authorize.cfm, and we are able to use monkehTweet to obtain the user's access token, secret and screen name values, which are again stored into the SESSION scope for use in the action page (post.cfm).
As we now have the user's access tokens, we dont need to send them to Twitter to authenticate every time we wish to access their account on their behalf. This means we can store their details in the database for future use, using the userID stored in the session scope to create the reference to our specific user record.
post.cfm
<!---
Name: post.cfm
Author: Matt Gifford AKA coldfumonkeh
(http://www.mattgifford.co.uk)
Date: 28.01.2011
Copyright 2011 Matt Gifford AKA coldfumonkeh.
All rights reserved.
--->
<cfscript>
/*
We also need to set the values into the
authentication class inside monkehTweets
*/
application.objMonkehTweet.setFinalAccessDetails(
oauthToken = session['accessToken'],
oauthTokenSecret = session['accessSecret'],
userAccountName = session['screen_name']
);
/*
Let's make a test call to update the
status of the authenticated user.
If you are using this for a number of users,
you will need to set the details prior to each call
using the setFinalAccessDetails() method above.
*/
/*
If you are using this purely for a single user,
you can set all of the authentication details in
the init() constructor method when instantiating the application
*/
returnData = application.objMonkehTweet.postUpdate(
"I'm using the awesome ##monkehTweets
ColdFusion library from @coldfumonkeh!"
);
</cfscript>
<cfdump var="#returnData#"
label="Returned data from the twitter request" />
In the final action page, we obtain the OAuth access token details from the SESSION scope. Of course, you could revise this code to run a query to look up the details within the database for the logged in user instead of storing them in a scope. The choice is yours.
Here, we send the details into the setFinalAccessDetails() method to set them into monkehTweet and to create the correct authentication headers to manage this user's account on their behalf.
Code Caveat
The code in this example contains a bizarre mix of script and tag-based coding, all forming a fully-functioning but bizarre cocktail of ColdFusion awesomeness. Don't question it, just go with the flow. It was more of a case that I was playing around with different techniques and options. For example, I dont often use the new Query() method for data transactions, so I took the chance to implement it here just to try something 'different'.
Variety, much like black pepper, is the spice of life.
Download the code
There you go; a very simple example using monkehTweet to handle multiple user accounts within an application, and storing access details for your users.
The full code, including .sql file to build a dummy database is included in the attached .zip archive for your enjoyment.