Managing multiple Twitter users authentication with monkehTweet

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.

 

comments powered by Disqus