http-auth

Node.js package for HTTP basic and digest access authentication.

basic

lib/auth/basic.js

Default setup module.

var defaults = require('../defaults');

Exporting module.

module.exports = Basic;

Basic Access Authentication.

  • param: String authRealm authentication realm.

  • param: Array authUsers array of users.

function Basic(authRealm, authUsers) {

// Realm.
this.realm = authRealm;
// Users.
this.users = authUsers;

// Used for async callback.
var self = this;

Applies basic authentication and calls next after user is authenticated.

  • param: Request request HTTP request object.

  • param: Response response HTTP response object.

  • param: Function next function that will be called after user is authenticated.

this.apply = function(request, response, next) {
	var authenticated = self.isAuthenticated(request, response);
	if(!authenticated) {
		self.ask(request, response);
	} else {
		next();
	}
}};

Checks authorization header in request.

  • param: Request request HTTP request object.

  • param: Response response HTTP response object.

  • return: Boolean true if is authenticated, else false.

Basic.prototype.isAuthenticated = function(request, response) {
	var authenticated = false;

	// If header exists.
	if("authorization" in request.headers) {
		var header = request.headers.authorization;
		var user = header.split(" ")[1];

		// Searching for user in user list.
		if(user) {
			authenticated = this.users.indexOf(user) != -1;
		}
	}

	return authenticated;
}

Asks client for authentication.

  • param: Request request HTTP request object.

  • param: Response response HTTP response object.

Basic.prototype.ask = function(request, response) {
	var header = "Basic realm=\"" + this.realm + "\"";

	response.setHeader("WWW-Authenticate", header);
	response.writeHead(401);
	response.end(defaults.HTML_401);
}

digest

lib/auth/digest.js

Default setup module.

var defaults = require('../defaults');

Utility module.

var utils = require('../utils');

Module for generating unique id.

var uuid = require('node-uuid');

Exporting module.

module.exports = Digest;

Digest Access Authentication.

  • param: String authRealm authentication realm.

  • param: Array authUsers array of users.

  • param: String algorithm algorithm for authentication.

function Digest(authRealm, authUsers, algorithm) {
	// Realm.
	this.realm = authRealm;
	// Users.
	this.users = authUsers;
	// Nonces.
	this.nonces = new Array();
	// Algorithm.
	this.algorithm = algorithm;

	// Used for async callback.
	var self = this;
 * Applies digest authentication and calls next after user is authenticated.
 *
 * @param {Request} request HTTP request object.
 * @param {Response} response HTTP response object.
 * @param {Function} next function that will be called after user is authenticated.
 
this.apply = function(request, response, next) {		
		// Processing authentication part.
		var authenticated = self.isAuthenticated(request, response, requestBody);
		if(!authenticated) {
			self.ask(request, response, requestBody);
		} else {
			next();
		}
	}
};

Checks authorization header in request.

  • param: Request request HTTP request object.

  • param: Response response HTTP response object.

  • param: String requestBody HTTP request body string.

  • return: Boolean true if is authenticated, else false.

Digest.prototype.isAuthenticated = function(request, response, requestBody) {
	var authenticated = false;

	// If header exists.
	if("authorization" in request.headers) {
		var header = request.headers.authorization;
		var co = this.parseAuthHeader(header);

		// Check for expiration.
		if(co.nonce in this.nonces) {
			// Sencond hash in digest access authentication.
			var ha2;
			// Calculating second hash.
			ha2 = utils.md5(request.method + ":" + co.uri);

			// Checking response for username.
			var userHash = this.users[co.username];

			// Username is correct.
			if(userHash && typeof userHash === 'string') {
				var ha1 = utils.md5(this.users[co.username]);

				// If algorithm is MD5-sess.
				if(co.algorithm == 'MD5-sess') {
					ha1 = utils.md5(ha1 + ":" + co.nonce + ":" + co.cnonce);
				}

				// If qop is specified.
				if(co.qop) {
					if(co.nc > this.nonces[co.nonce]) {
						// Updating nonce count.
						this.nonces[co.nonce] = co.nc;

						// Evaluating final authentication response.
						var authRes = utils.md5(ha1 + ":" + co.nonce + ":" + co.nc + ":" + 
							co.cnonce + ":" + co.qop + ":" + ha2);
						authenticated = (authRes == co.response);
					}
				} else {
					// Evaluating final authentication response.
					var authRes = utils.md5(ha1 + ":" + co.nonce + ":" + ha2);
					authenticated = (authRes == co.response);
				}
			}
		}
	}

	return authenticated;
}

Asks client for authentication.

  • param: Request request HTTP request object.

  • param: Response response HTTP response object.

  • param: String requestBody HTTP request body string.

Digest.prototype.ask = function(request, response, requestBody) {
	// Generating unique nonce.
	var nonce = utils.md5(uuid());
	// Adding nonce.
	this.nonces[nonce] = 0;
	// Scheduling async timeout function call.
	setTimeout(this.expireNonce, defaults.NONCE_EXPIRE_TIMEOUT, nonce, this.nonces);

	// Generating authentication header.
	var header = "Digest realm=\"" + this.realm + "\", qop=\"auth\", nonce=\"" + nonce + "\", 
		algorithm=\"" + this.algorithm + "\"";

	response.setHeader("WWW-Authenticate", header);
	response.writeHead(401);
	response.end(defaults.HTML_401);
}

Method for clearing not used nonces.

  • param: String nonce nonce to delete.

  • param: Array nonces array of nonces.

Digest.prototype.expireNonce = function(nonce, nonces) {
	delete nonces[nonce];
}

Method for parsing authorization header.

  • param: String header authorization header.

  • return: Array parsed array with authorization header data.

Digest.prototype.parseAuthHeader = function(header) {
	var headerOptions = new Array();

	// Replacing internal quotes.
	var searchHeader = header.replace(/\\"/g, """);
	// Padding with quotes not padding values.
	searchHeader = searchHeader.replace(/(\w+)=([^," ]+)/g, '$1=\"$2\"');
	// Initial tokens.
	var tokens = searchHeader.match(/(\w+)="([^"]+)"/g);

	// If tokens were found.
	if(tokens) {
		// Adding tokens to final Object.
		for(var i = 0; i < tokens.length; ++i) {
			var token = tokens[i];
			// Searching for first equal sign.
			var equalIndex = token.indexOf("=");
			// Extracting key.
			var key = token.substr(0, equalIndex);
			// Extracting value.
			var value = token.substr(equalIndex + 2, token.length - equalIndex - 3);
			// Adding to options.
			headerOptions[key] = value;
		}
	}

	return headerOptions;
}

defaults

lib/defaults.js

Module for default setups.

module.exports = {
 * Default HTML for not authorized page.
 
'HTML_401' : &quot;&lt;!DOCTYPE html&gt;\n&lt;html&gt;&lt;head&gt;&lt;title&gt;401 Unauthorized&lt;/title></head&gt;&lt;body&gt;&lt;h1&gt;401 Unauthorized&lt;/h1><p>This page requires authorization.</p&gt;&lt;/body></html&gt;&quot;,
 * Nonce expire timeout.
 
'NONCE_EXPIRE_TIMEOUT' : 3600000,
 * Default algorithm for Digest Access Authentication.
 
'DEFAULT_ALGO' : 'MD5'
}

http-auth

lib/http-auth.js

Authentication provider.

var provider = require('./provider');

Parser module for authentication input options.

var opt = require('./options');

Requests authentication instance from provider.

  • param: Array options options that may be used for authentication.

    • authRealm authentication realm.
    • authFile file where user details are stored in format {user:pass}.
    • authList list where user details are stored in format {user:pass}, ignored if authFile is specified.
    • authType type of authentication, digest | basic, optional, default is digest.
    • algorithm algorithm that will be used, may be MD5 or MD5-sess, optional, default is MD5.
  • return: Object authentication instance.

module.exports = function(options) {
	// Parsing options.
	var parsedOptions = opt(options);
	// Requesting new authentication instance.
	return provider.newInstance(parsedOptions);
};

options

lib/options.js

Modules.

var fs = require('fs'), utils = require('./utils'), defaults = require('./defaults');

Parsing input options for authentication.

  • param: Array options initial options.

  • return: Array array with parsed options.

module.exports = function(options) {
	// Checking for options.
	if(!options) {
		throw new Error(&quot;Authentication options are empty!&quot;);
	}
	
	// Checking authType.
	var authType = options['authType'];
	if(!authType) {
		authType = options['authType'] = 'digest';
	}
			
	if(!(authType in {'digest' : 1, 'basic' : 1})) {
		throw new Error(&quot;Authentication type may be digest or basic!&quot;);		
	}
	
	// Checking for algorithm.
	if(!options.algorithm) {
		options['algorithm'] = defaults.DEFAULT_ALGO;
	}
	if(!(options.algorithm in {'MD5' : 1, 'MD5-sess' : 1})) {
		throw new Error(&quot;Authentication algorithm may be MD5 or MD5-sess!&quot;);		
	}
	
	// Checking authentication realm.
	var authRealm = options['authRealm'];
	if(!authRealm) {
		throw new Error(&quot;Authentication realm is mandatory!&quot;);
	}

	// Authentication users.
	var authUsers = new Array();
	var authList = options['authList'];

	// If authFile is provided.
	var authFile = options['authFile'];
	if(authFile) {
		authList = fs.readFileSync(authFile, 'UTF-8').toString().split('\n');
	}
	
	// Checking authentication list.
	if(!authList || authList.length == 0) {
		throw new Error(&quot;Authentication list cannot be empty!&quot;);			
	}

	for(var i = 0; i &lt; authList.length; ++i) {
		var authLine = authList[i];

		if(authType == 'digest') {
			var authTokens = authLine.split(&quot;:&quot;);
			// Constructing A1 for digest access authentication.
			authUsers[authTokens[0]] = authTokens[0] + &quot;:&quot; + authRealm + &quot;:&quot; + authTokens[1];
		} else if(authType == 'basic') {
			// Pushing token to users array.
			authUsers.push(utils.base64(authLine));
		}
	}

	// Setting authUsers.
	options['authUsers'] = authUsers;

	return options;
}

provider

lib/provider.js

Digest and basic modules.

var Digest = require('./auth/digest'), Basic = require('./auth/basic');

Provider creates new basic or digest authentication instance.

module.exports = {

Creates new authentication instance.

  • param: Array options authentication options.

  • return: Object authentication instance.

'newInstance' : function(options) {
	if(options &amp;&amp; options.authType == 'digest') {
		return new Digest(options.authRealm, options.authUsers, options.algorithm);
	} else if(options &amp;&amp; options.authType == 'basic') {
		return new Basic(options.authRealm, options.authUsers);			
	} else {
		throw new Error(&quot;Invalid type, may be digest | basic!&quot;);
	}
}};

utils

lib/utils.js

Crypto module.

var crypto = require('crypto');

Utility module.

module.exports = {

Function for encoding string to base64.

  • param: String str string to encode.

  • return: String bas64 encoded string.

'base64' : function(str) {
	return new Buffer(str, 'UTF-8').toString('base64');
},

MD5 hash method.

  • param: String str string to hash.

  • return: String md5 hash of string.

'md5' : function(str) {
	var hash = crypto.createHash('MD5');
	hash.update(str);

	return hash.digest('hex');
}};