ColdFusion and OAuth Part 1 - Facebook

This post is more than 2 years old.

Last week, for some reason, I had multiple requests for an example of ColdFusion and OAuth integration. I ended up creating quick demos for Facebook, LinkedIn, and Google. This week I'll be blogging each in turn in the hopes that these entries can help others. Today, I'm going to share the Facebook code.

Before I begin, I want to warn folks. I wrote this code very quickly. It is not optimized. Also, the person I was helping was on ColdFusion 8. So the code isn't exactly what I'd call up to date. Of course, it will run just fine with ColdFusion 10. I typically assume that folks take my code samples here as just that - code samples - but I wanted to be more clear that this code would probably not be exactly best practices.

To begin, ensure you have access to Facebook's Developer portal (developer.facebook.com) and create a new application.

You can call it whatever you want, but the name should reflect your site in some way. Users will see this when your app launches so make it familiar. You can ignore the other two options.

On the next page, make note of your App ID and App Secret:

Click on "Website with Facebook Login" and enter a value for your site. You can, and probably should, use a local domain. In other words, you can enter something under localhost. Obviously this will change to a production URL once you're done, but for testing, localhost is fine. For my testing I used: http://localhost/testingzone/cf8fb and clicked Save Changes.

That's it for the Facebook side. Now let's talk about the OAuth process in general. I'm not going to go very deep into this as OAuth has been discussed elsewhere and my focus here is to demonstrate a ColdFusion example, but at a high level, the process looks like this:

  • Your site tells the user that you're going to send them to Facebook to authenticate/connect.
  • You create a "special" link that includes some required crap in the URL. Along with the required crap, you will have some optional crap. So for example, many OAuth providers ask you to spell out exactly what you want to use from the user. I.e., how much private data you require. Your link will include that, and Facebook will then warn the user. I.e., "This site wants to take your lunch money, read your email, and have relations with your significant other."
  • The user clicks and ends up at Facebook.com with a app-specific screen there. See the previous bullet point on how that screen may change.
  • The user clicks Yes or No (or approve or whatever).
  • Facebook sends you back to your site. In the URL will be a flag that you can check that will tell you if the user allowed your app. If they did, you will also have a special code.
  • You take that code, make a request (using CFHTTP) to Facebook, to get a secret access token.
  • This access token then allows you to get stuff. What stuff depends on what you asked for (see bullet point two).

That's it - roughly - and what's cool is that you will see this exact same (for the most part) process over my next blog entries as well.

With that in mind, let's look at the code. Again, I want to warn you this is a bit rough. First, the Application.cfc:

Obviously the app ID and app secret are removed above. The redirect URL will be used in a bit. Note that I've enabled session management as well. Now let's look at index.cfm.

There's two parts to this template. The first portion is run when you first get to the site and haven't connected to Facebook yet. I'm using a simple Session variable to track that. I provide a quick prompt and when clicked, I send the user to Facebook. Upon reflection, how I did this was kind of stupid. I could have just had the link in the original HTML link.

Speaking of the link, note the parts to it. client_id comes from our application settings. redirect_uri simply tells Facebook where to send the user when they OK/deny the connection. As I said, it is perfectly ok for this to be localhost during testing. The state variable is a security setting. I use a Session variable to store a UUID and ensure that the remote site (in this case, Facebook) sends back the same state. I could also use it for other things, like, well, state. Imagine I had 2 areas of my site where you could connect to Facebook. I could use this as a way to say, "I was in products", or "I was in music." Finally, the scope references what I'm asking for in terms of user privacy. Check Facebook's docs for more on what you can set in that regards.

Ok, so what happens when you click? Here is a screen shot.

Now let's look at redir.cfm, the main handler for the result from Facebook.

We begin by assuming that Facebook has return a code variable to us in the query string. We also validate the state variable I mentioned earlier. At this point, we need to get the Access Token from Facebook. This is done with a simple CFHTTP call. Note we pass back in the redirect URI. We aren't going back to redir.cfm again, this is just part of the security system.

If everything worked out ok, the result will be a string that looks like this:

access_token=AAAX&expires=5183804

That's where the string parsing code above comes into play. The access_token is what will give us access to the user's data.

If you return to the code sample above for index.cfm, you will notice that I've got a Facebook component tied to the user session. I can pass in the access token there and make use of it for future calls. I wrote this component very quickly. It doesn't have nice error handling or even pagination, but you can see an example of getting my profile as well as my friends.

I didn't spend much time on their API, but it seemed pretty darn easy to use.

You may be wondering - during testing - how do I get that permission screen to show up? You want to go to your Facebook Privacy settings, and then Manage Apps. This can be confusing because there is another link for this that brings you to your apps as a developer. You want to ensure you come here via your privacy settings, which will then help you manage your user app settings. Here's mine:

See my test app there on top? If you click the delete icon (x), you get this prompt:

This sounds like it may delete the app completely, but to be clear, this is user specific only. It is safe to confirm and test your application again.

Anyway, I hope this helps. I want to be clear that there's more involved here than what I've shown, but I wanted to share my sample app in the hopes others could use it.

Raymond Camden's Picture

About Raymond Camden

Raymond is a senior developer evangelist for Adobe. He focuses on document services, JavaScript, and enterprise cat demos. If you like this article, please consider visiting my Amazon Wishlist or donating via PayPal to show your support. You can even buy me a coffee!

Lafayette, LA https://www.raymondcamden.com

Archived Comments

Comment 1 by Edward Rynearson posted on 4/1/2013 at 11:08 PM

TY

Comment 2 by Raymond Camden posted on 4/1/2013 at 11:10 PM

YW :)

Comment 3 by Mario Rodrigues posted on 4/2/2013 at 1:21 AM

Great job on this Ray. Looking forward to reviewing the rest of the series.

Comment 4 by Richard posted on 4/2/2013 at 12:50 PM

great - just in time, I am working on a site that requires this stuff:)

Comment 5 by Bob Clingan posted on 4/2/2013 at 7:49 PM

Thanks for writing this series.. there is a project I've been itching to start using a site that utilizes oauth and I found it difficult to get started.

Comment 6 by Raymond Camden posted on 4/2/2013 at 7:53 PM

Part 2 was supposed to go live now, but I just blogged something else. It should be up around 4 though.

Comment 7 by David Jennings posted on 4/2/2013 at 8:04 PM

Great tutorial, thanks a lot :) I want connect this to database user - ie creating a registration form, is there any particular variable you would use to link the fb account to the database user? So they site can recognise them days later

Comment 8 by Raymond Camden posted on 4/2/2013 at 8:08 PM

Look at the result from the method called to get my own user details. It contains an ID. You could use that as a link to local tables. You may want to link it to username as well and ensure both match.

Comment 9 by Chris Pilie posted on 4/3/2013 at 7:09 PM

wondering if you are watching my google search keywords... reading my mind! thanks Ray.

Comment 10 by jeff horne posted on 4/4/2013 at 2:15 AM

Thanks. This is great. Looking forward to the rest of the series.

Comment 11 by Balriam posted on 4/7/2013 at 2:49 PM

Thanks
looks too easy and any one can understand easly.

Thanks
Balriam

Comment 12 by Mike posted on 4/10/2013 at 1:46 AM

Hi Ray,

Thanks for this I have managed to hook it into my site perfectly for a facebook login. One thing im struggling with though is getting it to work on my iphone (same script) it throws up an element state is undefined in session error. any ideas what might be going on here? beating my head off of my wall hasnt helped and has just made my mrs angry :)

Comment 13 by Joseph Potthast posted on 4/10/2013 at 4:42 AM

Finally got a chance to sit down and try this code. Ray as always you're amazing in how you A) seem to be able to read my mind about what I want to research and B) always explain things in such a way that you make it look so easy. Great job as always and thanks.

Comment 14 by Raymond Camden posted on 4/10/2013 at 4:32 PM

Mike, one issue a friend of mine ran into was using the wrong domain in the redir. By wrong I mean going from www.foo.com to foo.com. This caused the CF session to reset. Any chance that is happening to you? I assume not since you said it works ok outside of iPhone.

Comment 15 by Mike Sharp posted on 4/10/2013 at 8:11 PM

As always Ray, you are a scholar and a gent!.

That I think may be it, my redirect wasn't maintaining the www. and so it was dropping the session prior to firing the user to Facebook. forcing the www. seems to have resolved it nicely :)

Thanks!

Comment 16 by Steve posted on 4/17/2013 at 8:42 PM

Ray,

Thanks for the great post. I'm completely new to working with the Facebook API, and between your tutorial and the Graph Explorer I've been able to quickly and easily start getting data out of fb.

https://developers.facebook...

Comment 17 by Gilles posted on 4/17/2013 at 11:54 PM

Nice! Will you write one for Twitter too?

Comment 18 by Raymond Camden posted on 4/18/2013 at 12:00 AM

Wasn't planning on it. ;)

Comment 19 by Tomasz Mieczkowski posted on 4/22/2013 at 7:13 PM

Fantastic - I just grabbed Linked In - very clean code - no useless files - works like a charm. I'll give this one a shot too. Great work.

Comment 20 by Gary F posted on 4/24/2013 at 6:30 AM

Thanks for the tutorial, Ray. It's taking between 4 and 6 seconds to execute the whole demo even when I run it again. Shouldn't it be taking about 1 sec? :-( BTW, when trying to submit a comment it kept telling me it's been marked as spam. I'm on my 12th revision...

Comment 21 by Raymond Camden posted on 4/24/2013 at 6:38 AM

Well, if you are logged in, the index.cfm page runs getMe and getFriends. Both of those are network requests that could be slow. You could wrap then with cftimer calls or just enabled debugging to verify. Typically you would cache those results and not rerun them every time.

Sorry about the spam blocker going overboard on you.

Comment 22 by Nathan Blixt posted on 5/1/2013 at 5:21 PM

Works like a charm! However, what would be your method to automatically log in a user if their session expired but they're logged into Facebook? ...I might have found my answer: https://developers.facebook...

Comment 23 by Raymond Camden posted on 5/1/2013 at 5:36 PM

I believe if you autoforwarded them to the right link, they would auto bounce back.

Comment 24 by Dave White posted on 5/6/2013 at 7:21 AM

For those that asked about Twitter Oauth I have put this together
http://cfjquery.com/blog/po...

Comment 25 by Miles Prunier posted on 5/9/2013 at 10:32 PM

Raymond, would you have any idea why index.cfm is throwing this error?
Could not find the ColdFusion Component or Interface facebook.
The error occurred in /web/cf.telegram.com/html/test/f... line 13
13 : <cfset session.fbAPI = createObject("component","facebook").init(session.fbaccesstoken)>

Comment 26 by Raymond Camden posted on 5/10/2013 at 7:23 AM

It means it can't "see" the facebook CFC. Did you copy it?

Comment 27 by Miles Prunier` posted on 5/10/2013 at 4:07 PM

The Application.cfc file is in the same directory as index.cfm and redir.cfm. (Is there also a facebook.cfc?)

Comment 28 by Raymond Camden posted on 5/10/2013 at 6:50 PM

It is the last code sample above.

Comment 29 by Miles Prunier posted on 5/10/2013 at 7:02 PM

Gotcha. Thanks! (I REALLY appreciate it!)

Comment 30 by Seth Johnson posted on 5/31/2013 at 12:35 PM

Ray,

Thanks for the great start. I tried to work through the example but I think the code for your redir.cfm code is missing. Any chance you can repost?

Thanks,

Seth

Comment 31 by Raymond Camden posted on 6/1/2013 at 12:45 AM

The Gist that had the code borked out for some reason. I made a new Gist and replaced it above.

Comment 32 by Seth Johnson posted on 6/1/2013 at 2:02 AM

Thanks Ray!

Comment 33 by Michael posted on 7/5/2013 at 1:02 PM

Hi Ray! Thanks for the great tutorial.

I did a big and obvious copy and paste just to try and get your bear bones code working. When I login and get redirected back, I get the following error:

key [STATE] doesn't exist in struct (keys:urltoken,sessionid)

Seems state isn't defined in the session. Do you know how to solve this?

I'm using Railo if that makes a difference?

Thanks again!
Mikey.

Comment 34 by Raymond Camden posted on 7/5/2013 at 5:26 PM

Hmm. Well, it could be that you are losing your session when you leave the site and hit FB. Did you perhaps wait too long to click back?

Comment 35 by AJ Mound posted on 7/10/2013 at 6:53 PM

Raymond,

I've been looking for this for a while. Very useful! Was able to get this working very quickly.

I'd like to ask an aggressively stupid question (as I've never dealt with a CFC in a session variable before): How does one invoke this from a .cfm? I see the dump, but I've been unable to use the invoke tag to parse out any of the information.

Thanks

Comment 36 by Raymond Camden posted on 7/10/2013 at 6:55 PM

If session.foo was a component, then it would simply be:

result = session.foo.someMethod()

Comment 37 by AJ Mound posted on 7/10/2013 at 7:12 PM

Wow, you responded in two minutes! Thanks, Raymond!

Comment 38 by Michael posted on 7/29/2013 at 6:42 PM

Hi Ray,

I am still getting the error:

key [STATE] doesn't exist in struct (keys:urltoken,sessionid)

It seems that the session gets forgotten somehow. I might also add, that it IS initially set, because I am able to pass this to Facebook and then also have it returned in the URL.

However, when it comes to comparing the value of url.state to session.state ...it hits the error. Session is not defined.

Any idea how to solve this? I assume it happens because of a bug with cflocation?

I am using Railo.

Thanks,
Michael.

Comment 39 by Raymond Camden posted on 7/30/2013 at 1:50 AM

Yes, it does sound as if the session isn't sticking. Can you look at the cookies in the request before you access the session variable? Maybe do a quick cflog before you go and when you come back and see if they are changing.

Comment 40 by alessandro posted on 8/9/2013 at 7:03 AM

Hi. I've got this working on one of my sites, thanks!. When i try to run it on another subdomain of the site (same CF server, changed the FB appids etc) the session.state doesn't stick and bombs out on the redir.cfm page.. Any ideas? The sites area almost identical, sessionmanagement set as well. etc..

thanks

Comment 41 by Michael posted on 8/9/2013 at 11:41 AM

@alessandro there's definitely an issue with CF when saving session variables and then doing a redirect. The session will be created and you can even test for it, but when you redirect and come back, it's gone! Happens to me on Railo. Seems like it's a common bug and intermitent at that. In theory, you don't NEED this state part, but it's more secure that way.

I have founding saving session.state like so works better than saving it as part of a deeper nested session.

For me, session.state works, but if I did, session.facebookStuff.state it does not.

It's a weird one.

Comment 42 by Adriano Azevedo posted on 8/19/2013 at 7:09 PM

If facebook user made logout? How clean session or to verify if facebook user session is valid?

Comment 43 by Raymond Camden posted on 8/20/2013 at 1:37 AM

The call to login won't work if the session is expired. Nor would the call for user details.

Comment 44 by AJ Mound posted on 8/21/2013 at 6:15 PM

So, here's a fascinating error from CF/Facebook interaction. I created the world's simplest template that just outputs the URL variables sent to the page. This is the code:

<cfoutput>#testVar1#</cfoutput><br>
<cfoutput>#testVar2#</cfoutput><br>

When I run it through my server, it behaves normally:
http://myserver/facebook/wtf.cfm?testvar1=abc&testVar2=123
Returns: abc 123

This is the same server that I use for my FB application. So, when it's accessed in the correct way via facebook, It appends a slash ("/") to one of the variables like this:

https://apps.facebook.com/a...
Returns: abc 123/

Just curious if you think this is a result of FB messing with their server, or am I going crazy?

Thanks,
Andy

Comment 45 by Raymond Camden posted on 8/21/2013 at 6:19 PM

Not sure. Maybe they assume you aren't using URL params.

Comment 46 by s. pradhan posted on 4/21/2014 at 6:13 PM

Hello. This process was working fine till some user notified me that they are getting errors.
Do you have any idea why this is happening all of a sudden?

struct
Charset [empty string]
ErrorDetail I/O Exception: peer not authenticated
Filecontent Connection Failure
Header [empty string]
Mimetype Unable to determine MIME type of file.
Responseheader
struct [empty]
Statuscode Connection Failure. Status code unavailable.
Text YES

Comment 47 by Snake posted on 6/8/2014 at 10:57 PM

isDefined(), in 2013, tut tut, Mr Cameron would have a field day with that :-)

Comment 48 by Raymond Camden posted on 6/8/2014 at 11:28 PM

Mr Cameron can suck it. ;) I don't get into the whole structKeyExists/isDefined thing. I just use what comes to mind first.

Comment 49 by Marcel van Langen posted on 7/1/2014 at 3:57 PM

Great code, thanks. All of your code is working! Right now I'm trying to post a new post on my Facebook wall. I've put together the following code, but it is not giving the desired result. It's not posting and there is no error.

Any thoughts would be great!

http://pastebin.com/3PCYHrRK

BTW in my Pastebin are 2 possible solutions. Neither of them is working...

Comment 50 by Marcel van Langen posted on 7/1/2014 at 4:34 PM

It's getting better. I now realise I have to ask permission to publish by extending the scope to publish_actions and publish_stream. I've also changed the cfhttparam from URL to FormField. Still no luck though...

Comment 51 by Raymond Camden posted on 7/1/2014 at 7:15 PM

When you changed it to ask for the right permission did you get a better error message?

Comment 52 by Grant Powell posted on 12/16/2014 at 6:50 PM

Where is the code for the Facebook API? "createObject("component","facebook")"

Comment 53 (In reply to #52) by Raymond Camden posted on 12/16/2014 at 6:53 PM

In the code blocks above.

Comment 54 by Grant Powell posted on 12/16/2014 at 6:58 PM

Nevermind, I see it.

Comment 55 by Naveen Krishnamurthy posted on 5/4/2015 at 10:55 AM

Thank you for the post.

I have implemented facebook authentication for my application.I get the following error when I try to deploy the code in Production for Facebook authentication.

In Local testing environment, it works as a charm..

Note: I have made the encessary changes to the facebook app to point to the website

Error:

-------

The proxy server received an invalid response from an upstream server.

The proxy server could not handle the request GET /index.cfm.

Reason: Error reading from remote server

Can you please let me know anything you know about this?

Comment 56 (In reply to #55) by Raymond Camden posted on 5/4/2015 at 1:31 PM

It sounds like a network issue between production and Facebook. I'd check firewalls/etc on the production server.

Comment 57 by Anthony Escribens posted on 5/5/2016 at 11:37 PM

Hi Ray, it looks like the scope is not retrieving the user's email, even when I enter "email" in the scope. I'm testing it with my own account which I know I'm allowing my email to be used.

I got the 'name' and 'id' of the user which allows me to fetch the picture, but I really need the email. Any suggestions?

Comment 58 (In reply to #57) by Raymond Camden posted on 5/6/2016 at 1:07 AM

You got me there - I haven't done FB+CF since this post I think. Maybe check the app settings to see if it is blocked there so that even if you request the scope you can't get it.

Comment 59 (In reply to #58) by Anthony Escribens posted on 5/6/2016 at 1:30 AM

I checked the App Settings, email is allowed. There must be someone else keeping the email from being retrieved. I'll keep searching, thanks.

Comment 60 (In reply to #59) by Raymond Camden posted on 5/6/2016 at 1:33 AM

Please post back if you find it.

Comment 61 (In reply to #60) by Matt Zimmerman posted on 6/14/2017 at 3:44 PM

Did you guys ever discover a solution to this? I am getting the same issue.

Guess I should google a little better you can use this URL instead:

https://graph.facebook.com/v2.5/me?fields=id,name,email&access_token=#variables.accesstoken#
Comment 62 (In reply to #61) by dawesi posted on 10/12/2018 at 5:28 AM

you have to ask for it now as part of requested permissions, it's no longer automatic.