Getting GitHub's v3 API To Work With oAuth
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:
- Obtain an authorization (ie. oAuth token).
- Post Gist.
- 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.
Want to use code from this post? Check out the license.
Reader Comments
Ben,
You crack me up man...
"The code for my blog is pretty ghetto."
Love it!
@Steve,
Ha ha, glad you like :) Once of these days, I wanna give this baby-beast a full on makeover.
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.
@Jonathan,
Right - good point to bring up. I definitely wanted the Gists to be associated with my GitHub account. That just felt good :)
@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.
@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.