Last week I needed a Node.js web application to provide OAuth 2 access tokens under the authorization_code grant. I choose to use the node-oauth2-server, mainly because it is reasonably popular, does not require passport and it looks well-written.

Getting the integration to fully work was a bit of a struggle, since the library is somewhere between version 2.x and 3.x. Or otherwise stated, the library is version 3.x and the documentation is version 2.x. So I decided to write down what I needed to know, it might just help someone else.

In case you’re not familiar with OAuth 2.0, it’s a framework that enabled third-parties to obtain (limited) access to an HTTP service. It’s well described in RFC 6749.

Setup

The HTTP service is built with the Express web framework. There’s the express-oauth-server adapter to integrate node-oauth-server in an Express app.

The examples in this article are based on Express, though you should be able to follow along when using a different web framework.

First we must initialize the OAuth server. Don’t worry about the Model for now, I’ll get back to it in a bit.

const OAuthServer = require("express-oauth-server");
const oauth = new OAuthServer({ model: Model });

Then we mount the authorize and token endpoints, both on POST. A GETrequest on authorize should present your user (resource owner) with a form to authorize or deny the third-party. Note that you must ensure that all OAuth query parameters on the GET request (client_id, redirect_uri, et al) are also available on the POST request.

app.get("/oauth/authorize", function(req, res) {
    // render an authorization form
});
app.post("/oauth/authorize", oauth.authorize());
app.post("/oauth/token", oauth.token());

Then we can require a valid OAuth token on secure areas.

app.use("/secure", oauth.authenticate());

Model

The toughest part was providing the correct model to the OAuthServer. The documentation nor the examples of this model were apt, so mostly I reverse engineered this from the source.

For the authorization_code and refresh_token grants the model must implement the functions listed below.

Function Signature
getAccessToken (accessToken): Token
getAuthorizationCode (authorizationCode): AuthorizationCode
getClient (clientId, ?clientSecret): Client
getRefreshToken (refreshToken): Token
revokeAuthorizationCode (AuthorizationCode): Boolean
revokeToken (Token): Boolean
saveAuthorizationCode (AuthorizationCode, Client, User): AuthorizationCode
saveToken (Token, Client, User): Token
validateScope (scope): Boolean

getClient gets the clientSecret only when the client authenticates (i.e. when requesting or refreshing a token). When the clientSecret is provided getClient must validate it.

Note that any function may return its result wrapped in a Promise.

The data structures used by these functions are:

Token: {
    accessToken: String,
    accessTokenExpiresAt: Date,
    refreshToken: String,
    refreshTokenExpiresAt: Date,
    user: User
}

AuthorizationCode: {
    code: String,
    scope: String,
    expiresAt: Date,
    redirectUri: String,
    client: Client,
    user: User
}

Client: {
    id: String, // client_id
    clientSecret: String,
    grants: [ String ],
    redirectUris: [ String ]
}

User: {
    id: String
}

By default the generated tokens are the SHA1 hash of 256 random bytes. In case you want different tokens, you can implement generateAccessToken, generateAuthorizationCode, and generateRefreshToken in the model (with signature (): String).

Mapping custom sessions to users

In case the regular authentication flow or your web application does not use bearer tokens (e.g. when using session cookies), you need to implement your own handler to get the current user when POSTing to authorize. This is a function handle that maps a Request to a User (see above for its definition).

For example, load the User from your database by the sessionid:

function loadCurrentUser(req) {
    return db.getUserBySessionId(req.session.sessionid);
}

Then pass it in the authenticateHandler:

app.post("/oauth/authorize", oauth.authorize({
    authenticateHandler: {
        handle: loadCurrentUser
    }
}));

Final thoughts

node-oauth2-server is quite stable and while working my way through I didn’t run into any bugs I couldn’t fix by changing my model. Its assertions help a long way with debugging.

The lack of accurate documentation is made up for by its well-structured source code. You just have to read the source, Luke :)