Friday, August 28, 2015

Custom Basic Authentication and Authorization for Node Express

You have an existing REST API, and you wanted to protect it:

app.get('/api/something', (req: express.Request, res: express.Response, next: Function) => {
    res.json({message: 'Mensaje', description: 'Yeah!'});
});

The neatest way to protect your REST API is to make a function that returns a callback function for Basic Authentication so it can be chained on node express parameters, and the implementation detail can be hidden to user too. To wit, here's how it shall look like usage-wise:

import canBe = require('./CanBe');

app.get('/api/something', canBe.accessedBy(['guest']),  (req: express.Request, res: express.Response, next: Function) => {
    res.json({message: 'Mensaje', description: 'Yeah!'});
});


Most of the explanation of the use of bind shows its usefulness on the this object. The following code demonstrates another good use of bind function. What the called function accessedBy does is it returns the callback with a binded parameter (roles array parameter in the following example) by binding a leading parameter on the accessedByBind function; so when the callback function accessedByBind is called by node express, the roles parameter is passed to it too.


CanBe.ts:

/// <reference path="./typings/express/express.d.ts"/>

import express = require('express');

var basicAuth = require('basic-auth');


export function accessedBy(roles: string[]) : express.RequestHandler {
    return accessedByBind.bind(undefined, roles);
}


function accessedByBind(roles: string[], req: express.Request, res: express.Response, next: Function): express.RequestHandler {

    function unauthorized(res) : express.RequestHandler {
        res.set('WWW-Authenticate', 'Basic realm=Authorization Required');
        return res.send(401);
    }


    function isUserInRole(userName:string): boolean {

        var userRole: string;

        if (userName == "foo") {
            userRole = 'guest';
        }
        else {
            userRole = '';
        }

        var isAllowed = false;

        for(var i = 0; i < roles.length; ++i) {
            if (roles[i] == userRole) {
                isAllowed = true;
                break;
            }
        }

        console.log("Is allowed " + isAllowed)

        return isAllowed;

    }


    // === accessedByBind starts here ===


    var user = basicAuth(req);

    if (!user || !user.name || !user.pass) {
        return unauthorized(res);
    }


    if (user.name === 'foo' && user.pass === 'bar') {

        var isAllowed = isUserInRole(user.name);

        console.log("Is allowed " + isAllowed);

        if (isAllowed)
            return next();
        else
            return unauthorized(res);

    } else {
        return unauthorized(res);
    }

}


Here's .bind in a nutshell:

Live Code http://jsfiddle.net/b33gukgv/

function list(a,b,c) { 
    console.log('a: ' + a);
    console.log('b: ' + b);
    console.log('c: ' + c);    
    console.log(arguments);
}

//  Create a function with a preset leading argument
var always = list.bind(undefined, "Omni", "Present");

list("of", "good", "things");

always("something", "there", "to");

always("remind", "me");


Output:

No comments:

Post a Comment