Getting GitHub's v3 API To Work With oAuth

Posted June 21, 2012 at 9:49 AM by Ben Nadel

Tags: ColdFusion

For a few months, I've been using GitHub's Gist functionality as a way to color-code my blog entries. I host the vanilla code in the content of my blog; but, then I replace it (after the page has loaded) with the color-coded Gist version. This was working fairly well until about two weeks ago when I suddenly started getting "peer not authenticated" errors on my CFHTTP requests to the GitHub API.

Seeing as I hadn't changed anything on my end, I assumed the problem was on the GitHub side; so, I went to look at their API docs. From what I can tell, it looks like the version of the API that I was using (v2?) no longer supports Gist creation. Furthermore, v3 - the current version - requires oAuth-based authorization for Gist creation. I had been using Basic Authentication.

But, the "peer not authenticated" problem is not an API problem - it's a certificate problem. It looks like GitHub is now using a new SSL certificate for their API that doesn't come in-built with ColdFusion 8 (or 9). So, I went to Steven Erat's timeless blogpost on using Java's Keytool to import new SSL certificates.

At first, I kept getting the wrong SSL certificate (the one at developer.github.com); but, once I was able to get the api.github.com SSL certificate, importing it into my cacerts file fixed my "peer not authenticated" problem.

Once that was done, I had to figure out how to work within the v3 oAuth workflow. The code for my blog is pretty ghetto. I'd love to make it awesome - but, the fact is, it works and I don't have enough hours in the day! So, I settled on a rather awkward approach:

  1. Obtain an authorization (ie. oAuth token).
  2. Post Gist.
  3. Delete the authorization.

The reason that I am creating and then immediately deleting my authorization record is that I don't have a way to cleanly cache it in my application (remember, I'm doing this quick and dirty). As such, I need to delete the authorization record in order to prevent dozens of them from showing up in my GitHub account.

Here is the test code that I came up with:

  • <!--- Define the base URL of the v3 GitHub API. --->
  • <cfset baseAPI = "https://api.github.com/" />
  •  
  • <!---
  • Set up empty authorization values. These are the values that
  • we will have to clean up at the end - we'll be creating, using,
  • and then destroying an authorization token.
  •  
  • NOTE: This isn't the "right" way to do it - I simply don't have
  • an architecture that can store authorization tokens.
  • --->
  • <cfset authorizationID = "" />
  • <cfset authorizationToken = "" />
  •  
  •  
  • <!---
  • There's a number of parts of this workflow that could fail.
  • And, depending on where it fails, we may have to clean up the
  • Authorization that we have created. As such, wrap the workflow
  • in a Try/Catch so we have an opportunity to clean up.
  • --->
  • <cftry>
  •  
  •  
  • <!--- STEP 1: Get an authorization token. --->
  •  
  • <!---
  • Each token is associated with its own set of permissions.
  • For this demo, all we want to do is get "gist" authorization
  • (needed for Create permissions).
  • --->
  • <cfset postBody = {} />
  • <cfset postBody[ "scopes" ] = [ "gist" ] />
  • <cfset postBody[ "note" ] = "For single-use GitHub Gist creation." />
  •  
  • <!---
  • Get GitHub API Authorization. Note that we are using basic
  • authentication to obtain the authorization token.
  • --->
  • <cfhttp
  • result="apiRequest"
  • method="post"
  • url="#baseAPI#authorizations"
  • username="#username#"
  • password="#password#">
  •  
  • <cfhttpparam
  • type="body"
  • value="#serializeJSON( postBody )#"
  • />
  •  
  • </cfhttp>
  •  
  • <!--- Check to see if the API response failed. --->
  • <cfif !reFindNoCase( "2\d\d", apiRequest.statusCode )>
  •  
  • <!--- Failed to create authorization - stop processing. --->
  • <cfthrow type="AuthorizationFailed" />
  •  
  • </cfif>
  •  
  • <!--- Extract the API response payload. --->
  • <cfset authorization = deserializeJSON( apiRequest.fileContent ) />
  •  
  • <!--- Get the authorization data. --->
  • <cfset authorizationID = authorization.id />
  • <cfset authorizationToken = authorization.token />
  •  
  •  
  • <!--- STEP 2: Create the Gist. --->
  •  
  • <!--- Define the gist and its files. --->
  • <cfset postBody = {} />
  • <cfset postBody[ "description" ] = "oAuth exploration." />
  • <cfset postBody[ "public" ] = true />
  • <cfset postBody[ "files" ] = {} />
  • <cfset postBody[ "files" ][ "file1.txt" ] = {} />
  • <cfset postBody[ "files" ][ "file1.txt" ][ "content" ] = "Woot!" />
  • <cfset postBody[ "files" ][ "file2.txt" ] = {} />
  • <cfset postBody[ "files" ][ "file2.txt" ][ "content" ] = "Blam!" />
  •  
  • <!---
  • Post the Gist using the recently procured authorization
  • token. Rather than using Basic Authentication (as we did
  • above), we'll pass the oAuth token in the URL.
  • --->
  • <cfhttp
  • result="apiRequest"
  • method="post"
  • url="#baseAPI#gists">
  •  
  • <cfhttpparam
  • type="url"
  • name="access_token"
  • value="#authorizationToken#"
  • />
  •  
  • <cfhttpparam
  • type="body"
  • value="#serializeJSON( postBody )#"
  • />
  •  
  • </cfhttp>
  •  
  • <!--- Check to see if the API response failed. --->
  • <cfif !reFindNoCase( "2\d\d", apiRequest.statusCode )>
  •  
  • <!--- Failed to create gist - stop processing. --->
  • <cfthrow type="GistFailed" />
  •  
  • </cfif>
  •  
  •  
  • <!--- Catch any errors. --->
  • <cfcatch>
  • <!---
  • Nothing to do here - just need to make sure we don't
  • break out of the page flow before we have a chance to
  • clean up the Authorization.
  • --->
  • </cfcatch>
  •  
  •  
  • </cftry>
  •  
  •  
  • <!---
  • Now that the primary workflow has concluded, let's check to see
  • if we need to clean-up after ourselves. In this case, we only
  • need to do something if the Authorization Token was created.
  • --->
  • <cfif len( authorizationID )>
  •  
  • <!---
  • Delete the authorization token. This, again, uses Basic
  • Authentication.
  • --->
  • <cfhttp
  • result="apiRequest"
  • method="delete"
  • url="#baseAPI#authorizations/#authorizationID#"
  • username="#username#"
  • password="#password#"
  • />
  •  
  • </cfif>

NOTE: My username and password variables have been define in my Application.cfc.

Overall, the code is fairly straightforward; but, between the SSL problems and using a multi-step workflow, I thought it might be worth sharing. Definitely take all of this with a grain of salt as this was the explicitly the quick and dirty approach.




Reader Comments

Jun 21, 2012 at 10:22 AM // reply »
66 Comments

Ben,

You crack me up man...
"The code for my blog is pretty ghetto."

Love it!


Jun 21, 2012 at 10:26 AM // reply »
11,243 Comments

@Steve,

Ha ha, glad you like :) Once of these days, I wanna give this baby-beast a full on makeover.


Jun 21, 2012 at 10:55 AM // reply »
8 Comments

Looks good.

You don't need authorization to post a Gist. It's 100% optional. The only reason to have it is to tie it to your own personal account.

Also, If you don't want to cache the token, maybe you can cache the authorization ID? Then you don't have to create/delete new tokens, you can just call GET authorizations:id.


Jun 21, 2012 at 11:09 AM // reply »
11,243 Comments

@Jonathan,

Right - good point to bring up. I definitely wanted the Gists to be associated with my GitHub account. That just felt good :)


Aug 7, 2012 at 9:54 AM // reply »
1 Comments

@Ben - Hey man, I think I've posted a comment on another blog post relating to this but I'm in love with this idea of "unobtrusive gists" for bloggers. I don't understand why this isn't a WordPress plugin, etc.

It just seems ass backwards creating a gist first and then having to post a [gist id="123123"] in your blogpost and then parsing it.

Mainly just saying kudos to you for doing this because NOBODY ELSE is.

So much easier to post your code in your blog post and have some sort of magic happening behind the scenes handling the conversion to gist.


Aug 18, 2012 at 10:34 AM // reply »
11,243 Comments

@Joshua,

Thanks my man! I appreciate that. One of the side-effects that I really like about this approach is that if GitHub goes down or changes their authentication approach, my blog post will still render WITH code; the code just won't be nicely color coded - it will be just black-and-white.


Post A Comment

Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 23, 2013 at 5:19 AM
Ask Ben: Print Part Of A Web Page With jQuery
How to print also the background color of table cells and table lines ... read »
May 23, 2013 at 3:55 AM
Javascript Array Methods: Unshift(), Shift(), Push(), And Pop()
very interesting and helpful too. ... read »
May 22, 2013 at 5:35 PM
Script Tags, jQuery, And Html(), Text() And Contents()
This is still an issue 2 years later. jQuery is supposed to remediate these cross browser issues, no? I have been unable to find any statement from the jQuery team calling this behavior "by de ... read »
May 22, 2013 at 12:44 PM
Ask Ben: Query Loop Inside CFScript Tags
In cf10, if you call a function that has: local.result = {}; local.result.msg = ""; local.svc = new query(); local.svc.setSQL("SELECT * FROM..."); local.obj = local.svc.exe ... read »
May 22, 2013 at 12:29 PM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben: What version of Java are you using? Also, did you test users.id to see what Java reports as the data type? I wonder if it's not a Java primitive data type, but getting returned as something ... read »
May 22, 2013 at 11:47 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Dana, Awesome - so it looks like this bug was fixed in ColdFusion 10. Thanks so much for double-checking that. ... read »
May 22, 2013 at 11:37 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
When I c&p and run on cf10, I get: Selected User IDs: 1,4 User 1 selected: YES - YES User 2 selected: NO - NO User 3 selected: NO - NO User 4 selected: YES - YES User 5 selected: NO - ... read »
May 22, 2013 at 11:27 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Tom, Good thought, but no dice. Both of these still exhibit the same behavior: users.id[ users.currentRow ] users[ "id" ][ users.currentRow ] It's just something whacky happening with ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools