Accessing an API that uses Oauth

I’m trying to access the Noun Project API in Observable, but don’t know how to setup authentication. I tried to use a npm module, but it doesn’t seem to be accessible. This led me to the require module debugger, but the proposed solution still seems not to resolve.

Here’s a boilerplate notebook trying to use a Noun Project npm module

In general:

  • Why are some npm modules not usable in Observable? Are there credential passing stuff that is off limits?
  • I’m wondering how to access APIs like the Noun Project, which use Oauth for passing credential keys, in JavaScript. Is this possible in Observable, either in public or private notebooks?

Much appreciated, always learning,

Zach

The JS ecosystem has a variety of module formats (read more about them here). For a package to work in the browser (and especially with d3-require as used by Observable) its author needs to package it in a certain way.

the-noun-project has only been published as a CommonJS module, meaning it will only work in Node.JS. We can deduce this by taking a look at the beginning of its source:

var Client = module.exports = function (config) {
    var request = require('request'),
        Util = require('./util'),
// ...

Here we see two require calls, but no boilerplate to provide a require function - meaning, the code relies on Node’s native require.

In order to have any chance of using the package in the browser, it first needs to be bundled: all external dependencies need to be fetched and included into a source that has been rewritten (“transpiled”) to work in the browser. This is not always possible, as a package might depend on low-level libraries that are not available in the browser (mostly native Node libraries like fs and net).

There are some services that provide bundling for free, like bundle.run and skypack.dev. However, if we attempt to load the package via skypack, we’ll quickly see that we’ve hit the end of the road:

// NounProject = Error: [Package Error] "net" does not exist. (Imported by "tough-cookie").
NounProject = import('https://cdn.skypack.dev/the-noun-project')

Since the project’s API appears to allow cross-origin requests I would recommend to simply use fetch() in order to communicate with the API. Use a library like JSO to handle the OAuth part.

1 Like

Thanks for the explanation. There’s a lot going on!

It looks like JSO uses Oauth 2.0 while the Noun Project API uses Oauth 1.0a. Looking at the JSO setup of the client, they mention there is no use for the client secret since it uses implicit grant flow.

Apologies for my lack of knowledge, but is seems like I don’t have the required authorization setup.

After reading up a (tiny) bit on OAuth 1, I’m surprised they’re even using it, and I have my doubts that you’ll be able to make it work within Observable, as it involves a redirect back to the application. (*edit: Weirdly, TNP skips the access token, so that only the steps required for the request token are necessary).

In any case, you could try the oauth-1.0a package:

OAuth = (await import('https://cdn.skypack.dev/oauth-1.0a')).default

but it requires you to define your own hash function, and the documentation is sparse. One would probably have to take a look at the default behavior of the Ruby/Python libraries that are used in The Noun Project’s examples and try to apply it to oauth-1.0.


Ultimately you might have an easier time by creating a proxy server (e.g. via https://glitch.com) that uses the the-noun-project package to perform requests and exposes a minimal API. That would also allow you to protect your API credentials.

This demonstrates a way it can be done Oauth 2.0 Client Examples / Tom Larkworthy / Observable

1 Like

@tomlarkworthy Does that also work for OAuth 1.0a?

No, I could make an Oauth 1.0a client if it it was wanted though. They are conceptually similar and in my Oauth 2.0 client notebook I use the standard names (e.g. token_endpoint) so it’s fairly straightforward to see the commonalities.