What is the best way to implement a token-based authentication for restify.js?

user2440712 picture user2440712 · Aug 23, 2013 · Viewed 9k times · Source

I'm trying to build a RESTful api with restify.js, but I don't want to expose the api to everyone. And I'm going to use token-based authentication. The process in my mind is like this, I'm not sure whether it is reasonable.

  1. the user send username/password to an api to acquire the token.

  2. this token should be included in the request for the calls of every other api.

If this is reasonable, is there any node.js library I can use?

In addition, how do I protect the token? If someone intercept a http request with the token, then that person will get the api url and the token. Then he can send request as he wants. Is there a way to avoid this?

Thanks a lot!

Answer

Gajus picture Gajus · Dec 12, 2014

Basic access authentication

Restify is bundled with an authorizationParser plugin. authorizationParser parser out the Authorization. When the plugin is in use, it will make req.username and req.authorization properties available. Format of the latter is:

{
  scheme: <Basic|Signature|...>,
  credentials: <Undecoded value of header>,
  basic: {
    username: $user
    password: $password
  }
}

Your server will need to selectively intercept the requests that require authentication and validate user access credentials.

Here is an example server that will require authentication for all calls:

var restify = require('restify'),
    server;

server = restify.createServer();

server.use(restify.authorizationParser());

server.use(function (req, res, next) {
    var users;

    // if (/* some condition determining whether the resource requires authentication */) {
    //    return next();
    // }

    users = {
        foo: {
            id: 1,
            password: 'bar'
        }
    };

    // Ensure that user is not anonymous; and
    // That user exists; and
    // That user password matches the record in the database.
    if (req.username == 'anonymous' || !users[req.username] || req.authorization.basic.password !== users[req.username].password) {
        // Respond with { code: 'NotAuthorized', message: '' }
        next(new restify.NotAuthorizedError());
    } else {
        next();
    }

    next();
});

server.get('/ping', function (req, res, next) {
    res.send('pong');

    next();
});

server.listen(8080);

The easiest way to test is using curl:

$ curl -isu foo:bar http://127.0.0.1:8080/ping

HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 6
Date: Fri, 12 Dec 2014 10:52:17 GMT
Connection: keep-alive

"pong"

$ curl -isu foo:baz http://127.0.0.1:8080/ping

HTTP/1.1 403 Forbidden
Content-Type: application/json
Content-Length: 37
Date: Fri, 12 Dec 2014 10:52:31 GMT
Connection: keep-alive

{"code":"NotAuthorized","message":""}

Restify comes with inbuilt JsonClient that supports basic authentication, e.g.

var restify = require('restify'),
    client;

client = restify.createJsonClient({
    url: 'http://127.0.0.1:8080'
});

client.basicAuth('foo', 'bar');

client.get('/ping', function(err, req, res, obj) {
    console.log(obj);
});

OAuth 2.0

If you prefer the token authentication, then you can use restify-oauth2 package that implements Client Credentials authentication flow, which is what you are after.

The documentation page describes step-by-step how to setup such authentication, including roles of each endpoint, and there is a code example in their repository.

Summary

Regardless of which method of authentication you choose, all of them require you to use HTTPS. The difference is that if username/password is compromised, user would need to change their credentials. If token is compromised, then user would need to request a new token. The latter can be done programmatically, while the former usually relies on hardcoded values.

Side note. In production, credentials must be considered "compromised" if transferred at least once over an insecure channel, e.g. compromised HTTPS, as in case of SSL bug, such as Heartbleed.