A Chrome Extension template for reddit OAuth2 with AngularJS

Share Button

Like most programmers of the day I find myself spending entirely too much time on reddit.  Often it will occupy those moments while I’m eating my breakfast in the morning and shortly before my bedside lamp goes off for the night.  It’s a fantastic service and although it’s not all that original, it’s demonstrably run by good people who put their users first and the business second.. or at least it feels that way.  I’ve wanted to write an app for reddit for awhile now and only recently did I get off my butt and start working on an idea.

Before I could even really get into the nuts and bolts of my idea I had to figure out the best platform for write it in.  If you know me then you know I’m pretty open to trying new things.  I decided it would have been really cool if I could have written a client side AngularJS Single Page App (SPA) that I could host somewhere, but after some false starts trying to muddle through the reddit OAuth2 documentation by myself I eventually found my way to the helpful #reddit-dev community on Freenode. I found out that I cannot, in fact, complete the reddit OAuth2 workflow with reddit servers current configuration and the Cross Origin security of modern evergreen browsers.

I didn’t want this little setback to prevent me from writing my app as a SPA so I did the next best thing and settled on writing a Chrome Extension.  Chrome Extensions give you the best of both worlds: you can make Cross Origin requests fairly easily while allowing you to design your app like you would any other web app.

Since there doesn’t appear to be many examples of such a confluence of technologies that interact with reddit I thought I would document some of the caveats of how to do it and provide a template that people could use to get a head start.  If you’ve had enough of my blathering you can jump straight to the goods and clone the template I created that demonstrates how AngularJS can complete the reddit OAuth2 dance here. For more information on how to setup this template read on.

New reddit app types are here!

Reddit used to only have one kind of application type you could create (the web app).  In order to complete the OAuth2 dance with this app you had to have a web server running over http/https because only the http/https schemes were supported for callback URI’s.  This poses a problem for app developers because they may not have access to web server given the platform of their app.  A common way to workaround this limitation with OAuth2 on apps is to provide a custom scheme your app’s platform can use as a callback.  In Chrome Extension’s case this scheme is chrome-extension://.  Custom schemes were announced in /r/redditdev about 3 months ago.  You can read the announcement post by visiting the following.

[OAuth2] Custom schemes and other goodies

To support this functionality reddit devs created 2 new classes of apps.  All existing apps were retroactively labeled web apps and the two new types were the installed app and script app.  For a Chrome Extension you would choose to create an installed app so that you can complete the OAuth2 authorization with the Chrome Extension callback URI.

As a first step, login to reddit and create your own installed app.  Once it’s created you’ll need to make note of your client ID and secret as highlighted below.

reddit-edit-app

Since this is an installed app my credentials aren’t really a secret, so I don’t care if you know them!  Leave the redirect URI blank for now, we’ll figure out this address in the next section.  If you choose to actually release your app to the public you’ll need to create a second app for production, but that’s beyond the scope of this post.  For now, just create a single app that you can use for development purposes.

If you haven’t done so already then clone my reddit template from GitHub.  Open up the app/scripts/services/reddit-api.js file and populate the clientId and clientSecret with the appropriate values you found on your newly minted reddit app.

angular.module('redditOAuthTemplateApp')
  .factory('redditConfig', function redditConfig($window) {
    return {
      redditApp: {
        clientId: 'qWs6r0l6hXwQVA',
        clientSecret: 'hT3vlEu20FCqTyH3qDl7t2lf4Zg',
        scope: 'identity,read,mysubreddits',
        redirectUri: $window.chrome.extension.getURL('/') + 'app/index.html#/oauth/'
      }
    };
  })

Finding the app redirect URI

If you don’t already know the basic mechanics of how Chrome Extensions are developed then I would suggest reading the Getting Started: Building a Chrome Extension page.  For the impatient you could just skip to the Load the extension section to figure out how to load an unpacked project into your list of Chrome Extensions so you can debug your app.  Once you’ve loaded the app you can get the extension ID from the extensions list page, or you can open up the app and copy a fully qualified URI by doing the following.

Activate the extension by clicking the icon for the app beside the chrome smart bar.

chrome-ex

Once loaded, open up the debugger (F12) and look at the console to see the OAuth2 callback URI.

chrome-ex-callback

Copy this URI into the redirect URI setting of your reddit app and save it.

At this point the app should work.  You can test it by clicking the “Authorize App” button and completing the OAuth2 dance.  When you’re back at the app’s homepage the “Get User Info” button will be enabled.  Click it to make an example GET call to the reddit API (/api/v1/me).  To learn more about some of the caveats to about setting up reddit OAuth in a chrome extension read on.  For everyone else, you should be able to begin writing your own app and making reddit API calls!

reddit-user-info

Chrome extension configuration

In a Chrome Extension manifest.json file there are several things to be aware of that are critical to making reddit OAuth2 work.

In order to make Cross Origin calls you have to explicitly list the sites you will be calling in your permissions array.

Equally as important is configuring the extension to accept the browser redirect from reddit to the OAuth2 callback URI you configured.  You can achieve this by adding the path to the OAuth AngularJS hashbang address to the web_accessible_resources array.

{
  "manifest_version": 2,

  "name": "redditOAuthTemplate",
  "description": "A template for reddit OAuth in an AngularJS chrome extension",
  "version": "1.0",

  "permissions": [
    "https://ssl.reddit.com/",
    "https://oauth.reddit.com/"
  ],
  "browser_action": {
    "default_icon": "icon.png"
  },
  "background": {
    "scripts": ["chrome_events.js"],
    "persistent": false
  },
  "web_accessible_resources": [
    "app/index.html#/oauth/*"
  ]
}

POSTing to reddit’s API with AngularJS

Something that had me stumped for a little while was why reddit kept throwing back {"error": "unsupported_grant_type"} errors when I attempted to exchange a request token for an access token.  This required POSTing with a set of data (the request token, the redirect URI, and a grant type “authorization_code”), but whenever I posted back I would get a {"error": "unsupported_grant_type"} response when it was clear that the authorization_code grant type was supported.

After some head scratching I came across a post expressing similar troubles, but instead of using AngularJS in a Chrome Extension they were using jQuery in a PhoneGap app to retrieve an access token.  The OP eventually figured out the problem and was kind enough to share a code snippet of his jQuery.post call.  To my delight, this call worked and when I investigated the raw headers of the jQuery.post and my AngularJS $http.post I noticed right away that there were different content-type’s for the POST request payload.

Long story short is that AngularJS will by default POST with a content type of application/json whereas jQuery defaults to the more widely supported (and dated) content type of x-www-form-urlencoded.  I’m not sure why reddit only accepts this content type, perhaps it’s a requirement of the OAuth2 spec, but in order to support this method of POSTing data I had to use a new serialization method to format my POST parameters.  You can see this implementation in the app/scripts/services/reddit-api.js file with the function name of param. More details can be found in the following article.

Make AngularJS $http service behave like jQuery.ajax()

What next?

Well, I’m just about to get into the guts of my new reddit app.  Perhaps there are other caveats that I’ve yet to encounter.  If I discover anything of note I’ll amend this post with the details.  If you’ve read this far then you’re probably itching to write your own app. I wish you all the best and encourage you to share your experiences in the comments!

Share Button