DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports
Events Video Library
Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
View Events Video Library
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Integrating PostgreSQL Databases with ANF: Join this workshop to learn how to create a PostgreSQL server using Instaclustr’s managed service

Mobile Database Essentials: Assess data needs, storage requirements, and more when leveraging databases for cloud and edge applications.

Monitoring and Observability for LLMs: Datadog and Google Cloud discuss how to achieve optimal AI model performance.

Automated Testing: The latest on architecture, TDD, and the benefits of AI and low-code tools.

Related

  • Google Authentication for Your AWS Management Console With OAuth 2.0
  • When To Boost Your Authentication Strategy: Signs for Security Enhancement
  • Identity and Access Management Best Practices
  • Configuring SSO Using WSO2 Identity Server

Trending

  • How To Verify Database Connection From a Spring Boot Application
  • The Convergence of Testing and Observability
  • How To Validate Archives and Identify Invalid Documents in Java
  • Analyzing Stock Tick Data in SingleStoreDB Using LangChain and OpenAI's Whisper
  1. DZone
  2. Software Design and Architecture
  3. Security
  4. ColdFusion and OAuth Part 3 - Google

ColdFusion and OAuth Part 3 - Google

Raymond Camden user avatar by
Raymond Camden
·
Apr. 19, 13 · Interview
Like (0)
Save
Tweet
Share
4.70K Views

Join the DZone community and get the full member experience.

Join For Free

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.

component {
    this.name="googleoauthlogin4";
    this.sessionManagement=true;

    public boolean function onApplicationStart() {
        application.clientid="noyb";
        application.clientsecret="stillnoyb";
        application.callback="http://localhost/testingzone/googleoauthlogin/callback.cfm";

        application.google = new google(application.clientid, application.clientsecret);
        return true;
    }

    public boolean function onRequestStart(required string req) {

      if(!findNoCase("/login.cfm", arguments.req) && !findNoCase("/callback.cfm", arguments.req) && !session.loggedin) {
    		location(url="./login.cfm", addToken="false");
    	}
    	return true;
    }

    public boolean function onSessionStart() {
    	session.loggedin = false;

    	return true;
    }
}

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.

<cfset authurl = application.google.generateAuthUrl(application.callback, session.urltoken)>

<h1>Login Required</h1>

<p>
  In order to use this app, you must login with your Google account. Click to login below.
</p>

<cfoutput>
	<p>
	<a href="#authurl#">LOGIN!</a>
	</p>
</cfoutput>

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.

<!--- Validate the result --->
<cfparam name="url.code" default="">
<cfparam name="url.state" default="">
<cfparam name="url.error" default="">
<cfset result = application.google.validateResult(url.code, url.error, url.state, session.urltoken)>

<cfif not result.status>
  <!--- Imagine a nicer error here. --->
	<cfoutput>
		<h1>Error!</h1>
		#result.message#
	</cfoutput>
	<cfabort>
</cfif>

<cfset session.token = result.token>
<cfset session.loggedin = true>
<cflocation url="index.cfm" addtoken="false">

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:

<cfset me = application.google.getProfile(session.token.access_token)>

<h1>Home</h1>

<cfdump var="#me#" label="me">

Now let's look at the CFC:

component {

  public function init(clientid, clientsecret) {
		variables.clientid = arguments.clientid;
		variables.clientsecret = arguments.clientsecret;
		return this;
	}

	public string function generateAuthURL(redirecturl, state) {
		/*
		Scope is what you want to do with your access. Since this demo is ONLY for
		auth and user info, we have one hard coded value.
		*/
		return "https://accounts.google.com/o/oauth2/auth?" & 
				 "client_id=#urlEncodedFormat(variables.clientid)#" & 
     			 "&redirect_uri=#urlEncodedFormat(arguments.redirecturl)#" & 
				 "&scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code" & 
				 "&state=#urlEncodedFormat(arguments.state)#";

	}

	public function getProfile(accesstoken) {

		var h = new com.adobe.coldfusion.http();
		h.setURL("https://www.googleapis.com/oauth2/v1/userinfo");
		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());
	}

	/*
	I handle validating the code result from Google and automatically getting the auth token.
	I should be able to handle any bad result from Google or the user not allowing crap.
	I also validate the state.
	*/
	public struct function validateResult(code, error, remoteState, clientState) {
		var result = {};

		//If error is anything, we have an error
		if(error != "") {
			result.status = false;
			result.message = error;
			return result;
		}

		//Then, ensure states are equal
		if(remoteState != clientState) {
			result.status = false;
			result.message = "State values did not match.";
			return result;
		}

		var token = getGoogleToken(code);

		if(structKeyExists(token, "error")) {
			result.status = false;
			result.message = token.error;
			return result;
		}
		
		result.status = true;
		result.token = token;

		return result;
	}

	//Credit: http://www.sitekickr.com/blog/http-post-oauth-coldfusion
	private function getGoogleToken(code) {
		var postBody = "code=" & UrlEncodedFormat(arguments.code) & "&";
			 postBody = postBody & "client_id=" & UrlEncodedFormat(application.clientid) & "&";
			 postBody = postBody & "client_secret=" & UrlEncodedFormat(application.clientsecret) & "&";
			 postBody = postBody & "redirect_uri=" & UrlEncodedFormat(application.callback) & "&";
			 postBody = postBody & "grant_type=authorization_code";


			var h = new com.adobe.coldfusion.http();
			h.setURL("https://accounts.google.com/o/oauth2/token");
			h.setMethod("post");
			h.addParam(type="header",name="Content-Type",value="application/x-www-form-urlencoded");
			h.addParam(type="body",value="#postBody#");
			h.setResolveURL(true);
			var result = h.send().getPrefix();
			return deserializeJSON(result.filecontent.toString());

	}

}

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:

  public string function generateAuthURL(redirecturl, state) {
		/*
		Scope is what you want to do with your access. Since this demo is ONLY for
		auth and user info, we have one hard coded value.
		*/
		return "https://accounts.google.com/o/oauth2/auth?hd=camdenfamily.com&" & 
				 "client_id=#urlEncodedFormat(variables.clientid)#" & 
     			 "&redirect_uri=#urlEncodedFormat(arguments.redirecturl)#" & 
				 "&scope=https://www.googleapis.com/auth/userinfo.profile&response_type=code" & 
				 "&state=#urlEncodedFormat(arguments.state)#";

	}

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.

Related Blog Entries

  • ColdFusion and OAuth Part 2 - LinkedIn (April 3, 2013)
  • ColdFusion and OAuth Part 1 - Facebook (April 1, 2013)



Google (verb) security authentication

Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Google Authentication for Your AWS Management Console With OAuth 2.0
  • When To Boost Your Authentication Strategy: Signs for Security Enhancement
  • Identity and Access Management Best Practices
  • Configuring SSO Using WSO2 Identity Server

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends: