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,238 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,238 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,238 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 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
May 14, 2013 at 6:57 PM
The UX Of Prototyping: Low-Fidelity Is The New High-Fidelity
@Phillip, I'm not sure I follow what you mean? Are you saying that you looked at the list of widgets provided by the jQuery UI and let that be your style guide? ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools