ColdFusion and OAuth Part 2 - LinkedIn
Join the DZone community and get the full member experience.
Join For FreeLuckily, or I should say, obviously, since this is also an OAuth2 protocol, the code is almost the exact same. I literally took the Facebook demo, copied it over, and used that as a starting point. Like Facebook, LinkedIn has a developer portal (developer.linkedin.com) that includes documentation as well as a place to register applications. This may be a point of confusion for some. You may be thinking, "I'm not building an app, I just want to hook up with LinkedIn", but in general, both Facebook and LinkedIn treat these "applications" as a way to define your connection from your site to its own data.
Finding the place to add application is a bit weird. Once at the developer portal and logged in, notice you can click a down arrow next to your name in the upper right corner:
Click it, and then select API Keys.
On the next page click "Add New Application". The form here is somewhat intimidating. LinkedIn really could do a better job here, especially with using some defaults since I shouldn't have to re-enter the same data every time. I'd just bother with the required fields. Once set up, you'll want to make note of your OAuth keys. All you care about is the API Key and Secret Key:
Now we can switch to the code. As before, our process is going to be one of: Present a link. Users goes to LinkedIn. User is sent back with secret tokens of goodness. We get another token. Then we have the power to make API calls. (As a reminder, both this and the previous demo were written for ColdFusion 8. Hence the tag-based components.)
First, the Application.cfc - relatively an exact mirror of the Facebook code.
<cfcomponent output="false"> <cfset this.name = "cfb8linkedin"> <cfset this.sessionManagement = true> <cffunction name="onApplicationStart" output="false"> <cfset application.linkedin = structNew()> <cfset application.linkedin.apikey = "beer"> <cfset application.linkedin.secretkey = "isreallygood"> </cffunction> <cffunction name="onRequestStart" output="false"> <cfif isDefined("url.init")> <cfset onApplicationStart()> </cfif> </cffunction> </cfcomponent>
Next, the index.cfm code. Again, I'm presenting a link I assume my user will click. This is based on checking for a session variable that will be defined once the user finishes the OAuth process.
<cfif not isDefined("session.licode")> <cfif isDefined("url.startli")> <cfset session.state = createUUID()> <cfset redirurl = urlEncodedFormat("http://localhost/testingzone/cf8linkedin/redir.cfm")> <cfset liURL = "https://www.linkedin.com/uas/oauth2/authorization?response_type=code" & "&client_id=#application.linkedin.apikey#&scope=r_basicprofile%20r_network%20w_messages" & "&state=#session.state#&redirect_uri=#redirurl#"> <cflocation url="#liURL#" addToken="false"> </cfif> <a href="?startli=1">Login with LinkedIn</a> <cfelse> This is a LI user. <cfset session.liAPI = createObject("component","linkedin").init(session.liaccesstoken)> <cfdump var="#session.liAPI.getMe()#" label="me" expand="false"> <cfdump var="#session.liAPI.getFriends(count=40)#" label="friends"> </cfif>
As before, our links contains part of our access data, a list of permissions, as well as a URL stating where the user is sent back to. Probably the only interesting thing here is scope. That reflects the permissions and will change the prompt the user sees. You will want to modify this based on your needs.
Once the link is clicked, the user will see something like this:
Now let's look at redir.cfm. Again, this is virtually identical to the Facebook code, excepting that their result for the access token is nicely formatted in JSON as opposed to being a string you have to parse manually.
<cfif isDefined("url.code") and url.state is session.state> <cfset session.licode = url.code> <cfset redirurl = urlEncodedFormat("http://localhost/testingzone/cf8linkedin/redir.cfm")> <cfset liAccessTokenURL = "https://www.linkedin.com/uas/oauth2/accessToken?grant_type=authorization_code" & "&code=#session.licode#&redirect_uri=#redirurl#" & "&client_id=#application.linkedin.apikey#" & "&client_secret=#application.linkedin.secretkey#"> <cfhttp url="#liAccessTokenURL#"> <cfif isJSON(cfhttp.filecontent)> <cfset res = deserializeJSON(cfhttp.filecontent)> <cfset session.liaccesstoken = res.access_token> <cflocation url="index.cfm" addtoken="false"> <cfelse> <!--- This is an error case. ---> <cfdump var="#cfhttp#"> </cfif> <cfelseif isDefined("url.error_reason")> <!--- Handle error here. Variables are: url.error_reason and error_description ---> </cfif>
If everything works out, the user is sent back to the index.cfm page. If you remember, I called a linkedin component that is initialized with my access token. I built three methods for this CFC. One to get user data, one to get friends, and one to send messages. To be clear, their API supports more, but this is all my buddy wanted so that's all I built. Here's the component.
<cfcomponent output="false"> <cffunction name="init" access="public" returnType="linkedin" output="false"> <cfargument name="accesstoken" type="string" required="true"> <cfset variables.accesstoken = arguments.accesstoken> <cfreturn this> </cffunction> <cffunction name="getFriends" access="public" returnType="struct" output="false"> <cfargument name="start" type="numeric"> <cfargument name="count" type="numeric"> <cfset var httpResult = ""> <cfset var theURL = "https://api.linkedin.com/v1/people/~/connections?oauth2_access_token=#session.liaccesstoken#"> <cfif structKeyExists(arguments,"start") and isNumeric(arguments.start)> <cfset theURL &= "&start=#arguments.start#"> </cfif> <cfif structKeyExists(arguments,"count") and isNumeric(arguments.count)> <cfset theURL &= "&count=#arguments.count#"> </cfif> <cfhttp url="#theURL#" result="httpResult"> <cfhttpparam type="header" name="x-li-format" value="json"> </cfhttp> <cfreturn deserializeJSON(httpResult.fileContent)> </cffunction> <cffunction name="getMe" access="public" returnType="struct" output="false"> <cfset var httpResult = ""> <cfhttp url="https://api.linkedin.com/v1/people/~?oauth2_access_token=#variables.accesstoken#" result="httpResult"> <cfhttpparam type="header" name="x-li-format" value="json"> </cfhttp> <cfreturn deserializeJSON(httpResult.fileContent)> </cffunction> <cffunction name="sendMessage" access="public" returnType="boolean" output="false"> <cfargument name="recipients" type="any" required="true"> <cfargument name="subject" type="string" required="true"> <cfargument name="body" type="string" required="true"> <cfset var httpResult = ""> <cfset var x = ""> <cfset var m = structNew()> <cfset var recips = arrayNew(1)> <cfif not isArray(recipients)> <cfset arrayAppend(recips, recipients)> <cfelse> <cfset recips = recipients> </cfif> <cfset m["recipients"] = structNew()> <cfset m["recipients"]["values"] = arrayNew(1)> <cfloop index="x" from="1" to="#arrayLen(recips)#"> <cfset m["recipients"]["values"][x] = structNew()> <cfset m["recipients"]["values"][x]["person"] = structNew()> <cfset m["recipients"]["values"][x]["person"]["_path"] = "/people/#recips[x]#"> </cfloop> <cfset m["subject"] = arguments.subject> <cfset m["body"] = arguments.body> <cfhttp url="https://api.linkedin.com/v1/people/~/mailbox?oauth2_access_token=#session.liaccesstoken#" method="post" result="httpResult"> <cfhttpparam type="header" name="x-li-format" value="json"> <cfhttpparam type="body" value="#serializeJSON(m)#"> </cfhttp> <cfreturn httpResult.responseHeader["Status_Code"] eq 201> </cffunction> </cfcomponent>
Enjoy. Let me know if you have any questions. Tomorrow I'll write up the third entry in the series. That entry will discuss how to authenticate users with Google.
Published at DZone with permission of Raymond Camden, DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments