garann means > totes profesh

a defunct web development blog

etsy oauth in node

Sat, 30 Jun 2012 17:10:54 +0000

If you take one thing from this post, let it be: never believe anyone who says, "Oh, it's easy, you just use OAuth." I say this because we were in a meeting talking about how developers in the JavaScript hackathon I'm at today would authenticate to Etsy's API and someone, looking at the docs, made a similar statement. What I'm sure he meant was, "Oh, it's easy, you just use this OAuth library." The library in question, however, is in PHP. Luckily, there are similar libraries for Node, but they don't work exactly the same and no one had actually written sample code to let developers use them easily in their projects.

In the past, I've dealt with OAuth by using Everyauth, which is awesome when it works. However, the past couple days when I looking into this, it did not work. I forked the project and ran npm install and discovered several problems (possibly because I'm using 0.8.0). I tried to get around them and create an Everyauth module for Etsy anyway, but I couldn't get the damned thing tested so, though it looks right, I may never know if actually functions.

Everyauth is just a robust, consistent wrapper around another oauth module, however, so I didn't have to code the back and forth and passing of tokens and all the nonsense entirely manually. (Which I started to do before I realized I don't really have anything I need to punish myself for.) It was actually pretty simple to fix up a module using that to abstract the flow of getting the authentication token from Etsy, and probably would be the same for your site (assuming your site isn't OAuth2 cause then I don't even know).

create a skeleton module and make an oauth object

I actually started with all this crap in the server.js file and then moved it into a module once I knew it worked. Don't tell anyone. But when I did that second, responsible step, this is what the module file looked like:

var oauth = require("oauth").OAuth;

var etsyAuth = function etsyAuth( key, secret, domain, callback ) {

    var that = this;

    return this;
};

module.exports = etsyAuth;

I guess that's a pretty lazy module, but this is an academic exercise, and it gets the job done. The thing to note is that the only real setup necessary is including the OAuth module. That allows us to instantiate an OAuth object:

    this.o = new oauth(
        "http://openapi.etsy.com/v2/oauth/request_token",
        "http://openapi.etsy.com/v2/oauth/access_token",
        key,
        secret,
        "1.0",
        domain + ( callback || "/auth/etsy/callback" ),
        "HMAC-SHA1"
    );

Since this only has to work for Etsy, not every API under the sun, the host URLs can be hard-coded right in there, as well as most of the other initialization settings. The consuming app will obviously need to supply its own credentials, and might optionally change the route the application should redirect to once it receives the access token, but those are the only customizations offered. Well, that and the domain, but only because there might be some weird fuckers out there who intend to move their apps off of local.host:3000 at some point.

consume the oauth module

It probably makes sense to look at how this gets implemented. I used Express, but mostly just for easy session management. Aside from the routes, this server just imports Express and the OAuth code I wrote and instantiates and configures the Express app and the OAuth object:

var express = require( "express" ),
    etsyAuth = require( "./lib/etsy-auth" );

var app = express.createServer(),
    api_key = "your key here",
    api_secret = "your secret here",
    entry = "/auth/etsy",
    callback = "/auth/etsy/callback",
    o = new etsyAuth(
        api_key,
        api_secret,
        "http://local.host:3754",
        callback
    );

app.configure( function(){
  app.use( express.cookieParser() );
  app.use( express.session( { secret: "example secret" } ) );
  app.use( app.router );
  app.use( express.static( __dirname + "/public" ) );
});

This has all been sort of copying Everyauth, but it diverges a little bit at the routes cause I feel like entry and callback routes are really a part of your application, and OAuth is just a thing that happens there. So the app creates those, and passes control to the OAuth module within the request handlers:

app.get( entry, function( req, res ) {
    o.getRequestToken( req, res );
});
app.get( callback, function( req, res ) {
    o.getAccessToken( req, res, function ( req, res ) {
        res.redirect( "/success.html" );
    });
});

app.listen( 3754 );

In this case, the request handlers are both stupidly simple, but they could be doing data transformations, conditional templates, or generating Fibonacci sequences - whatever fits the needs of the application. They call two functions that are properties of our OAuth object, and the final bits of code I implemented/moved.

get some tokens

The first function, getRequestToken, uses the URL we provided when the OAuth object was instantiated and our API key to request a token, which is saved to the session object. Then the response redirects to a custom URL sent back where the user will approve our request to operate on their behalf:

    this.getRequestToken = function getRequestToken( req, res ) {
        that.o.getOAuthRequestToken( function( err, token, token_secret, results ){
            if ( err ) {
                console.log( err );
            } else {
                req.session.oauth = {};
                req.session.oauth.token = token;
                req.session.oauth.token_secret = token_secret;
                res.redirect( results[ "login_url" ] );
            }
        });
    };

The next function is called once the user clicks the big "Authorize" button and comes back to the callback route in our app. It uses our existing request token and the verifier to get an access token, which gets saved to the session like the request token. If the application passed in a callback, we go ahead and execute that, and we're done:

    this.getAccessToken = function getAccessToken( req, res, callback ) {
        if ( req.session.oauth ) {
            req.session.oauth.verifier = req.query.oauth_verifier;
            var auth = req.session.oauth;
            that.o.getOAuthAccessToken(
                auth.token,
                auth.token_secret,
                auth.verifier, 
                function( err, token, token_secret, results ){
                    if ( err ){
                        console.log( err );
                    } else {
                        req.session.oauth.access_token = token;
                        req.session.oauth.access_token_secret = token_secret;
                        if ( callback ) callback.call( this, req, res );
                    }
                }
            );
        }
    };

If you want to see how it all comes together, it's on github. Hopefully that's useful if you need to authenticate to Etsy or setup OAuth for your own API.