ColdFusion and OAuth Part 3 - Google

This post is more than 2 years old.

Welcome to my third and final blog post on ColdFusion and OAuth. (You may find the earlier entries below.) I apologize for the delay but I traveled last week so I'm a bit behind. If you have not yet read the earlier entries (part 1 and part 2), please do so as I will not be repeating the information I wrote about before.

So - hopefully you've gotten an idea of how easy OAuth can be. After I got things working right the first time, the second demo was quite easy. For this demo I decided to get a bit fancier. Google has an OAuth API that lets you authenticate against their servers. What if you wanted to use Google for your user system? In other words, skip the whole custom registration process and offload user management to Google. That's not only possible but actually one of the recommended use cases in their documentation.

Before we begin, please note that you will need to register your application with Google. This is exactly like what you did with Facebook and LinkedIn, except it is done at Google's API Console instead. By now this process should be easy enough where I don't need to explain it to you, just be sure to make note of the client id and client secret.

Since our application is going to use Google for login, I've created a simple Application.cfc that looks for a session variable, and if it doesn't exist, automatically pushes the user to a login page.

The onApplicationStart is virtually a carbon copy of the earlier examples, but the onRequestStart is new. It checks to see if we are logged in, and barring that, checks to see if we are requesting either the login page or the callback page. When the user first hits the application, they are sent to the login page:

Notice that there isn't a form here. Remember, we're sending the user to Google instead. I could have automatically pushed them, but I felt this was more friendly. Here's the code for that template.

I've put the Google OAuth code into a CFC to abstract a bit, but for now, don't worry about it. The link generation is very similar to the previous two examples. Once the user clicks login, they are sent to Google. In this case, Google recognized my account and preset the login, but I have the option to switch users as well.

As before, the user is sent to a callback page. Here is that template. Again note that I've put much more into the CFC now so this is somewhat simpler.

Finally, the user is directed to the homepage. As part of the Google API, I can get info about the user. I did so and dumped it out:

Here is that template:

Now let's look at the CFC:

For the most part I assume this is self-explanatory, but if anyone has any questions, let me know.

Finally, an interesting twist. What about businesses that make use of Google Apps? Turns out there is an undocumented solution for that. Look at the generateAuthURL function above. If you add the "hd" argument, you can specify a Google Apps domain:

This works great, but as I said, it isn't documented. A friend of mine is a paying customer of Google Apps and has reached out to tech support, but unfortunately, no one will give him a firm answer. His last email with them resulted in this:

Basically what the rep said is that their technical team said using hd=cbtec.com is a feature, so they didn't specify that in their documentation. He also said that the variable hd is also used to authenticate users in an organization with business units separated by domains.

Frankly that first sentence isn't sensible. "It is a feature and that's why we didn't document it." I'm hoping it was just a typo.

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 Craig Inman posted on 4/17/2013 at 11:42 PM

This does work really well. It has the domain under the field for username and won't let you enter any foreign characters, so you should not be able to enter any other Google App domains.

Comment 2 by Adrian Lynch posted on 4/29/2013 at 1:19 AM

Out of interest, what, if anything, do you store from the information provided by Google/Facebook/LinkedIn? The IDs? Profile photos?

Comment 3 by Dave White posted on 4/29/2013 at 3:39 AM

Great stuff (Again).
I liked the LinkedIn and Facebook posts as well and intend to use them all so users can login with whatever method they want.

I was wondering whether you were planning on doing a Sign in with Twitter post to complement the others ?

Comment 4 by Raymond Camden posted on 4/29/2013 at 6:10 PM

@Adrian: What you store would be up to you. What your app needs. Obviously you would have to content w/ the fact that those details may change.

@Dave: I looked into Twitter this weekend actually. Unfortunately they are OAuth1 so it isn't as easy. Fortunately, someone has already blogged on it so I don't have to. ;)

http://www.delbridge.org/po...

Comment 5 by Dave White posted on 4/29/2013 at 6:53 PM

Thanks Ray. You are a gentleman and a scholar.
Now I have to roll it all up and make a loginUsing.cfc
Also I have to agree that CodeSchool is excellent, hanks for the Hall Pass :)

Comment 6 by Joel Spriggs posted on 5/1/2013 at 4:26 PM

Nice post, explains it well. What would be your thoughts on utilizing that as the login interface for an app that uses primarily rest endpoints to connect a JS/html front-end with an ORM based backend?

Their api page does give information about token validation, so my thought is that every endpoint call requires the token be attached as a header element, and just cache the public keys from google to do validation against the tokens.

Comment 7 by Raymond Camden posted on 5/1/2013 at 6:49 PM

"What would be your thoughts on utilizing that as the login interface for an app that uses primarily rest endpoints to connect a JS/html front-end with an ORM based backend?"

Um, do you mean _your_ app is primary client-side code w/ CF REST stuff ont he back?

Comment 8 by Joel Spriggs posted on 5/1/2013 at 6:50 PM

Yes

Comment 9 by Raymond Camden posted on 5/1/2013 at 6:51 PM

Well, your client-side code could run a CFC that then hits Google. Basically the CFC acts as a proxy. Or you could hit Google directly if they support JSON/P or CORS.

Comment 10 by Tolo USN posted on 6/9/2013 at 12:27 AM

Once again - I love all of your posts. I'm a fan and a subscriber. Although I feel like I'm missing something here. This line in particular: new google(application.clientid, application.clientsecret); Do I need some sort of additional file to have this work? Does it matter if I use application.cfm instead of cfc? I feel so lost... :/

Comment 11 by Raymond Camden posted on 6/9/2013 at 7:25 PM

As I didn't provide a zip, you have to get the code from the blog entry. google.cfc is the second to last, where I say, "This is the CFC..."

You can use App.cfm instead of App.cfc, but... honestly, it is time to move on from App.cfm! :)

Comment 12 by Tolo USN posted on 6/12/2013 at 3:15 AM

Haha - I know. Sometimes I have much less to say about those things than I'd like to - ergo my limited experience with CFC files. Thanks Raymond - once again I owe you a beer. Actually it's a whole case by now. :D

Comment 13 by Raymond Camden posted on 6/12/2013 at 5:28 AM

Enjoy man. Warning - I've got pricey taste in beer.

Comment 14 by martin timmers posted on 7/4/2013 at 1:07 PM
Comment 15 by martin timmers posted on 7/4/2013 at 1:09 PM

Hi i just posted a question with a bit of code.

This got rejected so i posted a pastebin link.

Thanks for the tutorial.

Comment 16 by Raymond Camden posted on 7/4/2013 at 7:45 PM

It may be that the order isn't preserved in the second one and that may matter to Google. I've never used that particular API though so - sorry - can't really help.

Comment 17 by Misty posted on 8/31/2013 at 8:26 PM

Hi ray, Followed Your post, i am using Application.cfm rather than CFC

But when i logs into Google auth and it comes back to callback, it just hangs there

don't know what is the matter, any idea

Comment 18 by Raymond Camden posted on 9/3/2013 at 12:15 AM

So you come back to the CFM and... nothing happens? The CFM never outputs anything? Do you see anything logged at all?

Comment 19 by Misty posted on 9/3/2013 at 8:59 AM

Nothing gets logged, i used try catch blocks, site wide error, application based error, nothing. it just hangs up.

Comment 20 by Raymond Camden posted on 9/3/2013 at 2:34 PM

Then I'm not sure what to suggest. So to be clear, you leave google.com, see your server load in the browser, see the cfm in the browser bar, and nothing. If you add "Hello World" to the top of the CFM, does that come out?

Comment 21 by Misty posted on 9/3/2013 at 3:50 PM

It shows nothing, Nothing anywhere, not in console, not error anywhere where i can search of. i tried creating log also when a callback is returned but it does not write, something is going wrong

not sure what, if you wanna check

here is the link:

http://tinyurl.com/ktjdgh6

Comment 22 by Raymond Camden posted on 9/3/2013 at 3:53 PM

It worked for me. I see the dump in the popup window.

Comment 23 by Misty posted on 9/3/2013 at 4:22 PM

Strange, Did you checked twitter too

Comment 24 by Adrian Lynch posted on 9/3/2013 at 4:35 PM

Same here. I see a dump after submitting in the Google pop-up.

Comment 25 by Raymond Camden posted on 9/3/2013 at 5:36 PM

With Twitter processLogin.cfm loads, but displays nothing. Please share (pastebin or gist!) your code there.

Comment 26 by Misty posted on 9/3/2013 at 5:44 PM

ok, i thing something is going bad on my end, but not sure what, i tried different browsers, its a hosted application, i have to check again, but guys have you all the three of just one. Can you please confirm all 3 are working or just 1 or 2 only

Thanks

Comment 27 by Raymond Camden posted on 9/3/2013 at 5:46 PM

I tested two - and one showed what you saw. At this point it would be helpful to see your code. In a pastebin or gist.

Comment 28 by Misty posted on 9/3/2013 at 7:17 PM

Here is the Pastebin Code for Twitter:

http://pastebin.com/MvFue9Be

Comment 29 by Raymond Camden posted on 9/3/2013 at 7:23 PM

To be clear, are you using a different CFM file for each? I thought you were using the same. If you are, please do not share just a portion of your code, share *everything*.

Comment 30 by Raymond Camden posted on 9/3/2013 at 7:27 PM

Misty, did you debug this at all? Your code looks at the URL structure for a value called loginType. If you look at the value in the URL from Twitter, you can see login type is not there.

Sorry if that sounds rough, but as part of the debugging you should have done, you should have noticed that, right?

I'll echo my request that it would be more helpful if you were to share the entire CFM so we can see the app as it is being executed. Another test would be to add

<cfdump var="#url#" abort>

SO we can see what happens _immediately_ after the return from Twitter. You may have something else doing a cflocation later on in the process.

Comment 31 by Misty posted on 9/3/2013 at 7:49 PM

Hi Ray, Never took your comments as tough and harsh, I have always something new from you, so let me debug that and i will share the code also which i am using now

should i send the code in the email address

Comment 32 by Raymond Camden posted on 9/3/2013 at 7:55 PM

No, lets keep it here. If you have something secret in the CFM, like an APP Id, you can definitely change it.

Comment 33 by Misty posted on 9/3/2013 at 8:04 PM

i did some debugging and noticed that with twitter the application scope which i had defined as:

<cfcase value="twitter">
<cfset application.current_login_method = 'twitter'>
<cfset twitterLogin = objLogin.get_request_token()>
</cfcase>

is coming as [empty string] when it comes back from authorization, note here that i am not using the twitter4j library for authetication, do i need to use it

Comment 34 by Raymond Camden posted on 9/3/2013 at 8:19 PM

Don't know. I think this is why I skipped Twitter when I was doing my OAuth series.

Comment 35 by Misty posted on 9/3/2013 at 8:41 PM

ok, i will check this out and see what i can figure out here, so another question is: even yahoo can be done with oauth, have not debugged t yet, but just guessing

Comment 36 by Raymond Camden posted on 9/3/2013 at 8:44 PM

Cool - to be honest - I don't pay much attention to Yahoo. They have some cool APIs and stuff, but I just... well I just don't pay attention to em. Maybe I should. :)

Comment 37 by Fabio posted on 11/12/2013 at 4:06 PM

This tut did save my life! Thank you so much! ^___^

Comment 38 by Allan Johnson posted on 12/3/2013 at 3:34 AM

Hi Ray, Thanks for great tutorial and the tip at the end. Quick question.... Do you know of a way to access a Google folder's file list using OAuth & Coldfusion completed by the server only? No human clicking the agree and authenticate button? Looking to build an automated web app to reach into a Generic User's Google drive and access a folder where students have shared papers, presentations, projects, and artwork photos stored in their Google Drives. Thanks again!

Comment 39 by Raymond Camden posted on 12/3/2013 at 3:50 AM

Hmm. In theory, you could automate it with cfhttp. But you would need the user's email/password of course. Not sure how much faith I'd put into that though. Seems like a brittle solution.

Comment 40 by Allan Johnson posted on 12/4/2013 at 5:54 AM

Hi Ray, I agree as Google is dropping their client access in 2015. I am reading up on OAUTH for Google. I think from what I am gathering, there is a way to do this as long as I log in as the user 1 time and retrieve a valid refresh token for my app (which I did today). Now I just need to figure out how to get the whole thing to work. If I succeed, I'll post back. Thanks!

Comment 41 by Misty posted on 3/8/2014 at 2:26 PM

Hi ray, Coming back again, I am unable to access the google login API, I can find google + API, but not google Login, is it same or different, i user new google+ but did not worked

Comment 42 by Raymond Camden posted on 3/8/2014 at 5:55 PM

You need to give me more detail. What happens exactly?

Comment 43 by Misty posted on 3/9/2014 at 7:26 PM

Hi ray,

How to fetch additional Information of google API like email address, login username etc.

Comment 44 by Raymond Camden posted on 3/9/2014 at 9:08 PM

You've been asking similar questions on two posts - I replied on the other one.

Comment 45 by Misty posted on 3/11/2014 at 5:27 PM

how you write this in cffunction

public function getProfile(accesstoken) {

var h = new com.adobe.coldfusion.http();
h.setURL("https://www.googleapis.com/...");
h.setMethod("get");
h.addParam(type="header",name="Authorization",value="OAuth #accesstoken#");
h.addParam(type="header",name="GData-Version",value="3");
h.setResolveURL(true);
var result = h.send().getPrefix();
return deserializeJSON(result.filecontent.toString());
}

it will cfhttp call., but what about the following line:

h.send().getPrefix(); - only explanation need to this line

Comment 46 by Raymond Camden posted on 3/11/2014 at 5:55 PM

h.send() means, make the http call. getPrefix gets the result. So result (the variable) is equal to <cfhttp url="..." result="result">

Comment 47 by Misty posted on 3/11/2014 at 6:01 PM

hi ray,

you said, its possible to get email address, so check this link:

in pastebin, i will expire in 1 hours

http://pastebin.com/RVzey1uQ

This shows my Infor but does not show my email address info, if i try same in google editor which they had provided in the console window, the emails array is shown, what we are missing here then

Comment 48 by Raymond Camden posted on 3/11/2014 at 6:04 PM

I believe you will need to ask for the email scope. https://developers.google.c....

You should be able to add this to the existing scope.

Comment 49 by Misty posted on 3/11/2014 at 6:21 PM

and where i should add this

Comment 50 by Raymond Camden posted on 3/11/2014 at 6:23 PM

Um, did you read the blog post? It is one of the code samples, and documented. Please look again.

Comment 51 by Misty posted on 3/11/2014 at 6:33 PM

i followed exactly as your code, doing a copy/paste, but in your above demo too, no email address is showing,

Comment 52 by Raymond Camden posted on 3/11/2014 at 6:35 PM

You aren't getting it - the Scope value determines what you can get back. You need to *add* to it to get email support. See my comment a few things above.

Comment 53 by Misty posted on 3/11/2014 at 7:20 PM

okay, i read the full blog post, and the code too. even though it has ben specified anywhere about email and not even getting the one:

what if we have a code which is doing a call to the api like this:

<cffunction access="public" name="getProfileEmail" returntype="any" returnformat="json">
<cfargument name="accesstoken" default="" required="yes" type="any">
<cfhttp url="https://www.googleapis.com/..." method="get" resolveurl="yes" result="httpResult">
<cfhttpparam type="header" name="Authorization" value="OAuth #arguments.accesstoken#">
<cfhttpparam type="header" name="GData-Version" value="3">
</cfhttp>
<cfreturn DeserializeJSON(httpResult.filecontent)>
</cffunction>

but this just shows me an a string value:

https://www.googleapis.com/...

what i need to change here to bring actual email address instead of this string

Comment 54 by Misty posted on 3/11/2014 at 7:21 PM

read this line even though it has ben specified anywhere about email and not even getting the one:

as

even though it has not been specified anywhere about email and not even getting the one:

Comment 55 by Misty posted on 3/11/2014 at 8:07 PM

I tried again with this:

cfhttp url="https://www.googleapis.com/..." method="get" resolveurl="yes" result="httpResult">
<cfhttpparam type="header" name="Authorization" value="OAuth #access_token#">
<cfhttpparam type="header" name="GData-Version" value="3">
</cfhttp>
<cfoutput>#httpResult.filecontent#</cfoutput>

i get empty response as: {}

does it have any dependency on verified response

i tried in cflive.net

Comment 56 by Raymond Camden posted on 3/11/2014 at 9:20 PM

Misty: Do you, or do you not, see the code portion that involves Scope. It is above. The comment above is talks about how you have to change it. None of your comments seem to imply you get that you have to change the scope - so can you please answer that?

Comment 57 by Misty posted on 3/11/2014 at 9:43 PM

Ray, I am following what you are saying, but the code below google may not support after sept 2014

I use this:

<cfset googleLogin = objLogin.initiate_login(
loginUrlBase = "https://accounts.google.com...",
loginClientID = application.google_client_id,
loginRedirectURI = application.google_redirecturl,
loginScope = "https://www.googleapis.com/..."
)>

I get email with this, but if i use the https://www.googleapis.com/...

i do not get the email in that

Comment 58 by Raymond Camden posted on 3/11/2014 at 9:44 PM

So your saying - you don't want to use this method. You want to use the method I described in G+? Or are you saying something else? I'm really confused here.

Comment 59 by Misty posted on 3/11/2014 at 9:50 PM

I do not want to use the method of Google+ u described in another post, wanna use the method the comments are posted in.
i had to check many things before i make a complete rewrite of login with google+. right now, the old way of oauth2, going by google login side and fetching the contents

Comment 60 by Raymond Camden posted on 3/11/2014 at 9:57 PM

So... you don't want to use my method, and you don't want to use the G+ method. Then I don't know what to tell you. As I described, multiple times, the docs say you need to *ask* (via Scope) for email permission, and I've seen it work with my code. If you are using your own code, than I don't think I can help you more than I have tried to do so already.

Comment 61 by Joe Genshlea posted on 4/10/2014 at 7:39 PM

Great Post Raymond.

Any chance someone from the ColdFusion community could contribute code samples to Google's developer portal?

Comment 62 by Raymond Camden posted on 4/10/2014 at 7:45 PM

If somebody wants to submit it - I'm all for it.

Comment 63 by Ron Coulter posted on 5/3/2014 at 1:08 AM

I noticed you used localhost as the domain of your callback url. Is that actual code? My development server is behind a firewall, and it seems to be causing problems.
At first I kept getting uri mismatch errors. I found a workaround for that by adding a bit.ly url in my console and supplying that url as the callback parameter. That at least got me to Google's "Allow access" page. But then when I grant access I get "State values do not match" on the callback page.
Any thoughts? Thanks!
Ron

Comment 64 by Raymond Camden posted on 5/3/2014 at 6:59 PM

For oauth redirect, the remote server sends a header back to the browser saying,"Go Here." For my testing then, localhost works fine. In a 'real' app, you would not do that of course. Thing is - a firewall should not impact that. If your browser can hit the URL, then it should just plain work.

Comment 65 by Ron Coulter posted on 5/3/2014 at 7:55 PM

OK, Thanks. I'll take another look at the code with a fresh set of eyes Monday.

Comment 66 by Ron Coulter posted on 5/7/2014 at 12:28 AM

Sure enough, even after triple- and quadruple-checking my callback url, i still had a typo in it. Works fine now. Thanks for the help.

Comment 67 by Iftkhar posted on 6/28/2014 at 2:49 AM

How can I get user email ?

Comment 68 by Raymond Camden posted on 6/28/2014 at 6:34 AM

The scope value determines what data is returned. You have to ask for email.

https://developers.google.c...

Comment 69 by Amit Aggarwal posted on 11/22/2014 at 2:36 AM

I am having an issue with the `Google OAuth` using `cfhttp` in Safari. It works in my local environment but on our hosted environment everytime I use google oauth, I get the return code below:

Error detail:
string 400 Bad Request

File content:
string { "error" : "invalid_grant", "error_description" : "Code was already redeemed." }

Header:
>string HTTP/1.1 400 Bad Request Content-Type: application/json Cache-Control: no-cache, no-store, max-age=0, must-revalidate Pragma: no-cache Expires: Fri, 01 Jan 1990 00:00:00 GMT Date: Sat, 22 Nov 2014 01:38:30 GMT X-Content-Type-Options: nosniff X-Frame-Options: SAMEORIGIN X-XSS-Protection: 1; mode=block Server: GSE Alternate-Protocol: 443:quic,p=0.02 Transfer-Encoding: chunked

<cffunction name="getGoogleToken" access="public" output="false">
<cfargument name="Event" type="any">
<cfargument name="code" type="any">
<cfset var="" rc="Event.getCollection()"/>
<cfscript>
var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&";
postBody = postBody & "client_id=" & UrlEncodedFormat(rc.googleclientid) & "&";
postBody = postBody & "client_secret=" & UrlEncodedFormat(rc.googleclientsecret) & "&";
postBody = postBody & "redirect_uri=" & UrlEncodedFormat(rc.googlecallback) & "&";
postBody = postBody & "grant_type=authorization_code";
</cfscript>

<cfhttp url="https://accounts.google.com..." method="post" result="result" charset="utf-8">
<cfhttpparam type="header" name="Content-Type" value="application/x-www-form-urlencoded">
<cfhttpparam type="body" value="#postBody#">
</cfhttp>

<cfreturn deserializejson(result.filecontent.tostring())=""/>

</cffunction>

Comment 70 (In reply to #69) by Raymond Camden posted on 11/22/2014 at 2:22 PM

I'd Google "Code was already redeemed" and see what it suggests.

Comment 71 (In reply to #70) by Amit Aggarwal posted on 11/22/2014 at 2:26 PM

Thanks Ray. I tried that and it said its typically if the token is already used. The strange thing is it works perfectly fine in Firefox, Chrome and IE, it's just safari which is having an issue.

Comment 72 by Leo posted on 4/6/2016 at 1:38 PM

Hi Raymond..thanx for posting but i have one small question.. m new to cf + api and did not get the workflow behind your code.is it possible for you to provide at github with necessary files? and sorry but should i execute google.cfc with any cfm file or it will direct execute??

Comment 73 (In reply to #72) by Raymond Camden posted on 4/6/2016 at 1:46 PM

All the code that was used is in the blog article, so I can't provide more really.

"and sorry but should i execute google.cfc with any cfm file or it will direct execute??"

I'm not quite sure I understand what you are saying. If you don't know what CFCs are in general, I'd start with the ColdFusion docs to get the basics.

Comment 74 by Athelene posted on 2/23/2020 at 9:00 PM

Raymond, I realize this is an old post but I'm hoping you will be able to follow up. FYI, while I don't have this working just yet, it is the most instructional post I've found on the topic. Thank you!

I feel I've followed the post. I have the login.cfm file which provides the link to login. I get the Google authentication page. However, when I authenticate with Google, I come back to the login.cfm page, not the callback page I've registered with Google. I get no errors coming up. I decided to try putting the lines below on the login.cfm page that comes up to see if I'm even getting any return from Google but the test comes back that I have no token. I can't figure out a way to troubleshoot where this is going wrong without errors or any returned info. I can't even figure out how it can come back to the login.cfm page. Any thoughts or guidance?

<cfif isdefined("session.token")="">
I have a token.
<cfset me="application.google.getProfile(session.token.access_token)">
<cfdump var="#me#" label="me">
<cfelse>
I have no token.
</cfif>

One other note, I was testing the <cfoauth> tag earlier. While I didn't get that completely working either, it did at least send me to the callback page after authenticating with Google. So I'm pretty sure I have the correct callback page registered properly with Google.

Thanks in advance for any help you might have. Athelene

Comment 75 (In reply to #74) by Athelene posted on 2/23/2020 at 10:49 PM

Just wanted you to know that I've got it worked out. First problem described above was an idiot typing error. Then I had do to some updating of the endpoints and such. But the basic structure of your post was still extremely helpful. Thank you so much!!

Comment 76 (In reply to #75) by Raymond Camden posted on 2/24/2020 at 1:51 PM

Cool - glad to hear it.