Jribbble Three and Helping People OAuth

In December 2017, Dribbble announced version two of their API. In the same post, they set March 26, 2018 as the deprecation date of version one. I’ve maintained Jribbble for almost 8 years now. It’s a JavaScript library to help fetch data from the Dribbble API. In that time it’s gone through plenty of iteration to keep up with Dribbble API changes. Jribbble 3.0 continues that trend.

To date, Dribbble API changes have been additive. API features have given users more access to more of data via the API. As Dribbble explained in their post, version two goes the other way, it removes a lot of functionality.

The reduced functionality seems fine though. In my time working on Jribbble, most conversations I’ve had with designers are about using it to get their own shots. That’s still available, and the focus of API version two. My guess is most users of Jribbble won’t lose out. They will need to make code changes to continue using Jribbble though.

I’ve had a few conversations with folks on GitHub and email about updating to Jribbble 3.0. The biggest change is that Jribbble is no longer a jQuery plugin. It’s now a standalone library. I write more about the decision to remove jQuery later in this post.

The other big change is the shape of Jribbble’s public API methods. Here’s a 2.x vs 3.x example of a user getting their own shots with Jribbble:

// Jribbble 2.x
$.jribbble.setToken("<access_token>");
$.jribbble.users("tylergaw").shots().then(function(shots) { /* Work with shots JSON */ });
// Jribbble 3.x
jribbble.setToken("<access_token>");
jribbble.shots(function(shots) { /* Work with shots JSON */ });

The Jribbble API is no longer chainable or promise-based. Again, I detail more of the code decisions later in this post. We can still do the same thing, but now in a more concise way.

A New Barrier to Entry

With Dribbble API version one, we could make requests using read-only client access tokens. When creating an application via the Dribbble UI, they would generate the client access token. We could then use that token to make requests to the API.

In version two, those easy-to-use, auto-generated client access tokens are gone. Now, all requests must use a bearer token. To get a bearer token, you have to go through the OAuth2 flow.

Depending on your experience with OAuth, you might read that and think; “Sure, no problem. OAuth is easy” or “uhhhh, what’s an OAuth?” or somewhere in between. At any experience level, if you only want to make a single API request to display your Dribbble shots on your personal site, it’s a rigmarole.

When I first read about the new auth I thought, “welp, Jribbble is dead. As is all client-side JS access to the Dribbble API.” I thought this beause in most cases, you shouldn’t put OAuth-generated bearer access tokens in public code. Doing so makes them available for anyone to find and act on behalf of token owners in any–malicious–way they want.

I asked the Dribbble folks about this and they let me know that in this case, putting access tokens in public code would be OK because they’re read-only. A bad actor could still snag your access token and use it to abuse the API on your behalf. I’d guess by making a ton of nonsense requests. The Dribbble API has rate-limiting in place to help mitigate this. At the worst, Dribbble could revoke your access token or disable your app. You’d then need to go back though the process to get a new token and/or create a new app.

This change still didn’t sit well with me though. What are less code-savvy folks supposed to do? The main users of Dribbble and Jribbble are Designers. There’s a good chance many of them don’t have experience creating and deploying OAuth flows. That’s exactly who I’ve had conversations with over the years. And that’s who has reached out to me since the API version two announcment. Folks that know enough code to copy, paste, and tweak a snippet of JS, but not enough code to build and deploy a custom OAuth setup.

With that, I set out to come up with a way to help them. Because that’s what we do here.

Options

My first idea was to create a site where people would authenticate with a Dribbble application that I owned. They’d show up, tap “connect”, confirm with Dribbble, then get directed back to my site where they’d get an access token.

That would get the job done, but felt wrong. Having people authenticate through my app would mean their access tokens would be in my hands. If I deleted the app—or my Dribbble account—their tokens would stop working. If a bad actor generated a token then used it to abuse the API, my app could get suspended. Again, taking every user’s access token with it.

I needed a way for every Jribbble user to be able to generate access tokens using an application they owned.

Glitch

If you haven’t used it yet, Glitch is an excellent tool for sharing and learning code. What sets Glitch apart from similar code-sharing tools is that it gives you the ability to create and share web servers.

That’s exactly what I needed for this OAuth problem. I could write the server necessary for the OAuth flow, then make it available for anyone to–in the parlance of Glitch–“remix” the code to get full access to it. And they can do so without even creating a Glitch account.

So that’s what I did. jribbble.glitch.me is template that anyone can remix to get their own OAuth flow for generating bearer tokens to use with Dribbble API version two.

I won’t say that writing a server for this is “easy” because it’s only easy if you already know how to do it. But, I hope that this will help folks see that it’s attainable. To demonstrate, I’m going to plop the entire server here. The code is under 100 lines. The rest is comments to help people understand what’s happening at each step along the way:

const express = require("express");
const tiny = require("tiny-json-http");
const nunjucks = require("nunjucks");
const cookieParser = require("cookie-parser");

// We store the relavant Dribbble URLs here for convenience.
// authUrl is passed along to our template in the `/` handler below.
const authUrl = "https://dribbble.com/oauth/authorize";
const tokenUrl = "https://dribbble.com/oauth/token";

// The path of the Callback URL we set when creating our application on Dribbble.
// We reference this URL below in an app.get
const callbackUrl = "/oauth_callback";

// This isn't oauth or dribbble specific, but storing the value of the original
// Glitch project to check if we're seeing the original or a remix.
const ogGlitchUrl = "https://jribbble.glitch.me";

// These environment variables need to be set in the `.env` file. Look to your left 👈
// client id is safe as public value, if those are seen, it's OK.
// client_secret is not safe for public. You need to keep that private at all times.
const client_id = process.env.DRIBBBLE_APP_ID;
const client_secret = process.env.DRIBBBLE_APP_SECRET;

// When we receive an access_token from the api.dribbble.com server, we'll store it
// in this variable.
// Q: Why do we use `let` here instead of `const`?
// A: We use `let` so we can reassign `access_token` to a new value. Here, we set
//    an initial value of `null`. Below in the `/oauth_callback` handler we set
//    it to a new value of the access_token from the server.
let access_token = null;

// Standard express setup code.
const app = express();
app.use([express.static("public"), cookieParser()]);

// Set up our template library.
// I–Tyler–didn't look too deep into this, this block of code came from the nunjucks
// docs and got me up and running, so good enough for me at this time.
nunjucks.configure(["views", "public"], {
  autoescape: true,
  express: app
});

// This is our homepage and the page that does most of the work.
app.get("/", (req, res) => {
  const pageUrl = `https://${req.get("host")}`;

  // Here, we'll try to set the access_token from a cookie.
  // In the callback handler below, we set the access_token cookie on successful auth.
  // This isn't something you need to do in your Jribbble uses.
  access_token = req.cookies.access_token;

  // We use render so we can pass along variables to our template.
  // In index.html any time you see  or {% %}, we're referencing
  // a variable we set here.
  res.render("index.html", {
    authUrl,
    accessToken: access_token,
    clientId: client_id,
    // Just in case we've hit an authentication error we'll use this to display a message in the template
    error: req.query.error,
    // We create new boolean value here so we don't send the actual secret to the template.
    // Note: I–Tyler–am not sure this is 100% necessary, but it felt best to be overly
    // cautious when our app secret. You don't want anyone to have that.
    hasClientSecret: client_secret.length,

    pageUrl,
    isRemix: pageUrl !== ogGlitchUrl,
    callbackUrl
  });
});

// This is where our Dribbble applications will come back to after a GET to authUrl
app.get(callbackUrl, async (req, res) => {
  const data = {
    code: req.query.code,
    client_id,
    client_secret
  };

  try {
    // We required `tiny` above in tiny-json-http
    // That's a small http library I preferred to use https://github.com/brianleroux/tiny-json-http
    // It's not the only way to make requests, there are many different was to accomplish
    // this http post request to Dribbble
    // Note we are using async/await here. If you're unfamiliar, that's OK. The number one thing
    // to know is `await` makes this code act like it's pausing here and waiting for the http
    // request to complete before moving on to the following lines of code.
    const { body } = await tiny.post({ url: tokenUrl, data });

    // As mentioned above, here we're assigning access_token a new value that is your
    // shiny oauth access token that gives you public read access to your Dribbble account
    access_token = body.access_token;

    // NOTE: Setting a cookie want be required in your uses of Jribbble, because you will
    // include the access_token in your JavaScript.
    res.cookie("access_token", access_token);

    // We don't want to stay on the /oauth_callback page, so redirect back home.
    res.redirect("/");
  } catch (err) {
    // If we hit an error we'll handle that here
    console.log(err);
    res.redirect("/?error=😡");
  }
});

app.get("/logout", (req, res) => {
  res.clearCookie("access_token");
  res.redirect("/");
});

app.listen(process.env.PORT);

Again, you can remix this at jribbble.glitch.me to get full access to the code.

Another cool thing. This example is intended for the Dribbble API, but with a few minor changes, it will work for any standard OAuth flow.

If you use that and run into trouble–or think it’s great–let me know on Twitter.

The Jribbble Rewrite

The main part of this project was writing Jribbble 3.0. Jribbble 2.x has six public methods to access resources from Dribbble API version one. Many of those methods have subresources like comments, likes, et al. It also has a chainable, promise-based interface. All these things add a non-trivial amount of plumbing code to make them possible.

Dribbble API version two reduces functionality to getting the current user’s profile, shots, and projects. For Dribbble-approved applications you can get a user’s likes and a list of popular shots.

The reduced functionality meant I could cut most of Jribbble’s code. The first thing I did was audit the usage of jQuery. The only jQuery methods in use were; $.ajax, $.Deferred, and $.extend. Because of the limited usage, I decided Jribbble 3.0 would not use jQuery.

It also didn’t feel necessary to use any type of transpiling process. So no Babel or TypeScript or the like. I would only write good-ole browser JavaScript. Also, I wanted 3.0 to work in as many browsers as possible, so I only used old-timey JS. No arrow functions, no let or const, etc. It wasn’t that bad. This is a small library so restraining myself to older JS wasn’t a problem. And writing it directly instead of relying on Babel keeps the file size smaller.

I could have used newer features and still probably ended up with wide-enough support, but it was a fun challenge. And it reminded me of writing JS in years past.

This was enough of a rewrite that I opened a new, blank file and started writing instead of reusing 2.0 code.

3.0 Details

Like 2.0, this version also has six public methods; setToken, shots, user, projects, likes, and popular. Each method is available on the window-scoped jribbble object.

setToken is the same as in 2.0. It’s how users give Jribbble their access tokens.

jribbble.setToken("12345");

For 3.0 I decided to also allow users to provide their token as an option when calling any of the other methods. For example:

jribbble.shots({token: "12345"}, callback);

That accomplishes the same thing as setToken in a more concise way.

From here, I’ll detail three internal functions that handle the lion’s share of what Jribbble can do.

Function one: get

I took it back in time for this one. For day-to-day work I use fetch or a wrapper like Axios to make network requests. For Jribbble I didn’t want to use a third-party library or polyfills for requests. So instead of fetch, I went back to XMLHttpRequest.

All Jribbble requests have the same requirements so I was able to abstract the functionality to a common function. I use the internal get function for all requests to the Dribbble API:

var get = function(path, callback) {
  var url = "https://api.dribbble.com/v2/" + path;
  var req = new XMLHttpRequest();
  req.addEventListener("load", function() {
    if (callback) {
      if (typeof callback === "function") {
        var ret = {};

        if (this.status < 400) {
          try {
            ret = JSON.parse(this.responseText);
          } catch (err) {
            ret = {
              error: "There was an error parsing the server response as JSON"
            };
          }
        } else {
          ret = {
            error:
              "There was an error making the request to api.dribble.com.",
            status: this.status
          };
        }

        callback(ret);
      }
    }
  });
  req.open("GET", url);
  req.setRequestHeader("Authorization", "Bearer " + accessToken);
  req.send();
};

Not bad. It’s a standard XMLHttpRequest GET request with an Authorization header. The bulk of the function is guard code to protect against type errors, JSON parsing problems, and network errors. It does what it can to fail with grace if there is a problem.

I use the Authorization header to send along the user’s Dribbble access token with every request.

The get function will work back to IE7. I figured It’d be safe to not include the old fork to check for ActiveXObject. It’s been years since I’ve typed that, it’s giving me a good chuckle to see it here.

Function two: createApiMethod

The user, projects, likes, and popular methods all do the same thing. They process N-number of arguments, make a request to the Dribbble API, and call a user-provided callback. The callback receives a single argument, the JSON response from the request.

Because they’re all similar, I didn’t want to have to repeat the same code when defining each method. Instead, I abstracted the functionality to createApiMethod.

var createApiMethod = function(path) {
  return function() {
    var args = processArguments.apply(null, arguments);
    get(path + args.query, args.callback);
  };
};

The path parameter is passed along to get to build the URL to the Dribbble API.

I then define each public method as a member of the api object. Each is a function with a unique path based on its needs.

var api = {
  ...
  user: createApiMethod("user"),
  projects: createApiMethod("user/projects"),
  likes: createApiMethod("user/likes"),
  popular: createApiMethod("popular_shots")
};

This doesn’t provide any extra functionality. It only makes it so I don’t have to repeat as much code when defining methods. createApiMethod returns a function. Let’s look at what’s happening in the body of that function.

var args = processArguments.apply(null, arguments);
get(path + args.query, args.callback);

The first line is the heavy lifting. It’s function number three that I’ll write about next. For now, it’s important to know that it returns an object with query, callback, and resourceId keys.

Those values, plus the path argument, let us build the arguments needed to make the request to Dribbble with get.

Function three: processArguments

For me, this is the most interesting bit of code in Jribbble. Every public Jribbble method except setToken uses processArguments. Its purpose is to inspect all arguments passed to Jribbble methods.

To show its usefulness, consider the following usage examples:

jribbble.shots(
  "456789",
  {token: "12345"},
  function(shotObject) { /* Work with JSON */ }
);

jribbble.shots(
  {token: "12345", page: 3, per_page: 5},
  function(shotsArray) { /* Work with JSON*/}
);

jribbble.projects(
  function(projectsArray) { /* Work with JSON */ },
  {token: "12345"}
);

jribbble.user(
  function(userObject) { /* Work with JSON */ }
);

Notice in each I’m providing a different number of arguments of different types. And in the case of projects I’m providing the arguments in a different order. This type of flexibility isn’t possible with a typical method signature. One where I define each parameter when I create the method.

For example, imagine a method signature for jribbble.shots like this:

var shots = function(shotId, options, callback) { /* Do the work */ };

That would work for the first usage example, but what about the second? What if I don’t need a single shot and I don’t need to provide an options argument?

This is where processArguments comes into play.

First, notice when I call the function in createApiMethod I use apply:

var args = processArguments.apply(null, arguments);

This took me a few minutes to get my head around. It’s also hard to write about, but here goes. I need the function that createApiMethod returns to take zero to three arguments. The Jribbble user, provides those. Using apply let me pass along the arguments object received from calls to public API methods. Then, within processArguments I have access to that original arguments object. Once I have it, I convert it to an array for manipulation:

var args = [].slice.call(arguments);

An important thing to note is processArguments doesn’t define parameters:

var processArguments = function() {...};

Again, that’s not something I can define ahead of time. This whole dance lets the public methods be flexible in number and order of parameters. It lets them say “yeah, whatever you got, I’ll take it.”

I mentioned earlier, processArguments returns an object with query, callback, and resourceId keys. The work of the function focuses on creating that object.

While I can’t define parameters, I do know a few things about potential arguments. I know any callback should be a function and any options should be an object. I also know resourceId is the identifier for a Dribbble shot. A shot id can be a string or a number.

I use that knowledge to inspect each item in the args array I created from the arguments object. Depending on the type I assign the item’s value to a local variable.

...
var resourceId = null;
var opts = {};
var callback = function() {};
...
for (var i = 0; i < args.length; i += 1) {
  switch (typeof args[i]) {
    case "string":
    case "number":
      resourceId = args[i];
      break;
    case "object":
      opts = args[i];
      break;
    case "function":
      callback = args[i];
  }
}

That snippet sets two out of three variables this function needs to return. resourceId and callback are ready to to go. I only use resourceId in jribbble.shots. The Dribbble API has different paths if you’re requesting a single shot or a list of shots. I use the value–or lack of a value–of resourceId to determine the path.

What about the third item, query? Also what is this opts object in favor of?

In earlier examples, I showed providing an object with token, page, and per_page keys to public methods. Token is the most important one. If a user doesn’t provide an access token, they can’t make requests. After the for loop I check for a token key on the opts object:

if (opts.token) {
  accessToken = opts.token;
}

I define accessToken at the root level of the main Jribbble function. This is how I provide the flexibility of setting token with the setToken method or via an options object.

At this point in processArguments, if there’s no value for accessToken there’s no reason to continue. I throw an error and let the user know they need to update their code.

if (!accessToken) {
  throw new Error(
    "jribbble needs a valid access token. You can either include this as an option: jribbble.shots({token: '1234'}) or with jribbble.setToken('1234')"
  );
}

If the user provided page or per_page, those keys will also be on the opts object. query needs to be a string that I can append to the URL of any request. So, I need to create that string if necessary. I know that “page” and “per_page” are the only query parameters allowed because they’re documented. I can use that knowledge to build the string based on the user-provided values.

var params = ["page", "per_page"]
  .map(function(p) {
    return opts[p] ? p + "=" + opts[p] : null;
  })
  .filter(function(i) {
    return i;
  })
  .join("&");

For each item in the array check to see if that key exists in opts. If it does, return a string beginning with the item plus an equals sign plus the value in opts. If the key isn’t in opts return null.

At this point, we could have an array that looks like:

["page=4", null]

That’s if the user set a page, but not a per_page value.

Next, I use filter to remove any null values. And finally join the items of the array with an ampersand between each. In the above example we’d end up with “page=4”.

processArguments is ready to return an object with the three keys needed. I also need to do one last thing for query. If the user doesn’t need a query, return an empty string. If they do, prefix the params string with a “?” so it’s ready to append to a URL.

return {
  resourceId: resourceId,
  callback: callback,
  query: params ? "?" + params : ""
};

And that’s processArguments. Syntax and even code-wise it’s not fancy, but it does a job and does it in what I think is a clear way.

One more iteration complete

This was another iteration on a little library I keep going. It has a small userbase, but I know it provides value for them when it’s needed. As the Dribbble API changes, I’ll keep Jribbble in sync as best I can.

Again, the OAuth flow project is at jribbble.glitch.me or just jribbble.com. The Jribbble source is available on GitHub. If you run into trouble, please open an issue or submit a pull request.

Thanks for reading