Skip to main content
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: David Epler
Ben Nadel at InVision In Real Life (IRL) 2019 (Phoenix, AZ) with: David Epler ( @dcepler )

Getting GitHub's v3 API To Work With oAuth

By on
Tags:

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.

Want to use code from this post? Check out the license.

Reader Comments

27 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.

15,674 Comments

@Jonathan,

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

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.

15,674 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.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel