Thursday, August 21, 2014

HapiJS - Autenticacion REST con hapi-auth-bearer-token plugin

Introducción

En este articulo explicamos el uso de Bearer token plugin para HapiJS.

API  publico o privado?

Un REST API recurso puede ser privado si por medio de routing lo podemos crear  de uso exclusivo para una aplicacion de server. En este caso cada llamado debe ser de HapiJS UI server page a REST API. Dado que Rutha utiliza ambos, y nuestra aplicacion es un SPA (Single  Page Application), la opcion de API privado no es factible. En este caso optamos por utilizar autenticacion a nivel de REST.

Modelo ideal segun Twitter de autenticacion 'Application Only'

Segun Twitter, son tres pasos que se requieren:
  • Una aplicacion codifica la llave y secreto del usuario (consumer key and secret) en un set de credenciales. Esto es lo que adquieres en la mayoria de los OAuth APIs y el cual debes incluir en tus aplicaciones.
  • Una aplicacion crea una llamada POST a un recurso o 'endpoint' OAuth2/token para intercambio de token (bearer token o token al portador).
  • Para acceder al API REST, la aplicacion utiliza el token al portador para autenticar.

Usando HapiJS

En este ejemplo no explicaremos completame el flujo, solo nos enfocamos en el punto #3, en donde autenticamos llamados al API REST. El punto 1 y 2 para aplicaciones no publicas como Twitter, es posible con la autenticacion del usuario generar un token al portador permitiendo al usuario acceder el API REST que requiere ser autenticado.

En el bootloader de HapiJS

server.pack.register(require('hapi-auth-bearer-token'), function(err) {
server.auth.strategy('token', 'bearer-access-token', {
validateFunc: function(token, callback) {
// read from db or some place
var matched = false;
var tokenResult = { token: token };
var err = null;
if (token === 'a1b2c3') {
matched = true;
} else {
tokenResult = null;
err = { error: 'Unauthorized' };
}
return callback(err, matched, tokenResult);
}
});
// health check
server.route({
method: 'GET',
path: '/api/health',
config: {
handler: function(req, reply) {
reply('OK');
}
}
});
server.pack.register(controllers,
{
route: {
prefix: '/api'
}
}, function() {
if (!module.parent) {
server.start(function () {
console.log('Server started at port ' + server.info.port);
});
}
});
});
view raw gistfile1.js hosted with ❤ by GitHub
Registramos el plugin de 'hapi-auth-bearer-token' y la estrategia con validateFunc, que contiene la logica del token. Esto se puede reemplazar por un datastore en Redis o MongoDB que almacene la sesion, o algun otro metodo criptografico.

Rutas

En las rutas, toda ruta que requiera previa autenticacion, le asignamos en config.auth el nombre de la estrategia a utilizar.
var debug = require('debug')('users');
var createHandler = require('./v1/create.js');
exports.register = function(plugin, options, next) {
plugin.route({
method: 'POST',
path: '/v1/users',
handler: createHandler,
config: {
auth: 'token'
}
});
next();
};
exports.register.attributes = {
pkg: require('./package.json')
};
view raw gistfile1.js hosted with ❤ by GitHub


Consumidor del API

Finalmente, en el cliente nos aseguramos de enviar el header de autorizacion con el token al portador.
/*globals expect:true*/
var Hapi = require('hapi');
var server = require('./../../../lib/hapi');
describe("Users controller", function() {
it("should return HTTP 401 when no bearer header auth is found", function(done) {
server.inject({ method: 'POST', url: '/api/v1/users' }, function (res) {
expect(res.statusCode).toBe(401);
done();
});
});
it("should return HTTP 500 when bearer code is a mismatch", function(done) {
server.inject({ method: 'POST', url: '/api/v1/users', headers: { authorization: "Bearer abc" } }, function (res) {
expect(res.statusCode).toBe(500);
done();
});
});
it("should return HTTP 200 for /api/v1/users", function(done) {
server.inject({ method: 'POST', url: '/api/v1/users', headers: { authorization: "Bearer a1b2c3" } }, function (res) {
expect(res.statusCode).toBe(201);
done();
});
});
});
view raw gistfile1.js hosted with ❤ by GitHub



Hasta la siguiente entrega
Rogelio Morrell


No comments:

Post a Comment