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

1

11

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!

Thorsten answered 23/8, 2013 at 21:13 Comment(3)
If you can use https you will not get into such middle-man attack problems. As long as you are on http the token is gonna be compromised.Palmieri
Thank you. I've heard that using https will have some performance tradeoff. Is this the only solution? And regarding to my other question, is there an existing library for token-based authentication in node.js? Thanks!Thorsten
passportjs.org it has support for oauthPalmieri
V
26

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.

Vitiligo answered 12/12, 2014 at 10:58 Comment(3)
Should correct req.username to req.authorization.basic.username.Sayed
How do I apply this method to only authenticate certain routes but allow access to others?Epistemology
@DeanMeehan Add the server.use line after the insecure routes, but before the secure ones.Lipophilic

© 2022 - 2024 — McMap. All rights reserved.