Permalink
Browse files

Example

  • Loading branch information...
1 parent ecdefe6 commit 3873162f8495361ec51f4ec234ecb09319ac1200 @hueniverse committed Nov 25, 2012
Showing with 255 additions and 33 deletions.
  1. +127 −1 README.md
  2. +70 −0 example/usage.js
  3. +15 −15 lib/hawk.js
  4. +2 −1 package.json
  5. +41 −16 test/hawk.js
View
128 README.md
@@ -1,6 +1,7 @@
![hawk Logo](https://raw.github.com/hueniverse/hawk/master/images/hawk.png)
-**Hawk** is HTTP authentication scheme using a message authentication code (MAC) algorithm to provide partial HTTP request cryptographic verification.
+**Hawk** is HTTP authentication scheme using a message authentication code (MAC) algorithm to provide partial
+HTTP request cryptographic verification. For more complex use cases such as access delegation, see [Oz](/hueniverse/oz).
Current version: **0.0.x**
@@ -10,6 +11,8 @@ Current version: **0.0.x**
# Table of Content
- [**Introduction**](#introduction)
+ - [Protocol Example](#protocol-example)
+ - [Usage Example](#usage-example)
# Introduction
@@ -39,3 +42,126 @@ Digest, this mechanism is not intended to protect the key itself (user's passwor
and server both have access to the key material in the clear.
+## Protocol Example
+
+The client attempts to access a protected resource without authentication, sending the following HTTP request to
+the resource server:
+
+```
+GET /resource/1?b=1&a=2 HTTP/1.1
+Host: 127.0.0.1:8000
+```
+
+The resource server returns the following authentication challenge:
+
+```
+HTTP/1.1 401 Unauthorized
+WWW-Authenticate: Hawk
+```
+
+The client has previously obtained a set of **Hawk** credentials for accessing resources on the "http://example.com/"
+server. The **Hawk** credentials issued to the client include the following attributes:
+
+* Key identifier: dh37fgj492je
+* Key: werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn
+* Algorithm: hmac-sha-256
+
+The client generates the authentication header by calculating a timestamp (e.g. the number of seconds since January 1,
+1970 00:00:00 GMT) and constructs the normalized request string (newline separated values):
+
+```
+1353832234
+dh37fgj492je
+GET
+/resource/1?b=1&a=2
+127.0.0.1
+8000
+some-app-data
+```
+
+The request MAC is calculated using the specified algorithm "hmac-sha-256" and the key over the normalized request string.
+The result is base64-encoded to produce the request MAC:
+
+```
+/uYWR6W5vTbY3WKUAN6fa+7p1t+1Yl6hFxKeMLfR6kk=
+```
+
+The client includes the **Hawk** key identifier, timestamp, and request MAC with the request using the HTTP "Authorization"
+request header field:
+
+```
+GET /resource/1?b=1&a=2 HTTP/1.1
+Host: 127.0.0.1:8000
+Authorization: Hawk id="dh37fgj492je", ts="1353832234", ext="some-app-data", mac="/uYWR6W5vTbY3WKUAN6fa+7p1t+1Yl6hFxKeMLfR6kk="
+```
+
+The server validates the request by calculating the request MAC again based on the request received and verifies the validity
+and scope of the **Hawk** credentials. If valid, the server responds with the requested resource.
+
+
+## Usage Example
+
+Server code:
+
+```javascript
+var Http = require('http');
+var Hawk = require('../lib/hawk');
+
+
+// Credentials lookup function
+
+var credentialsFunc = function (id, callback) {
+
+ var credentials = {
+ key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
+ algorithm: 'hmac-sha-256',
+ user: 'Steve'
+ };
+
+ return callback(null, credentials);
+};
+
+// Create HTTP server
+
+var handler = function (req, res) {
+
+ Hawk.authenticate(req, credentialsFunc, function (err, isAuthenticated, credentials, ext) {
+
+ res.writeHead(isAuthenticated ? 200 : 401, { 'Content-Type': 'text/plain' });
+ res.end(isAuthenticated ? 'Hello ' + credentials.user : 'Shoosh!');
+ });
+};
+
+Http.createServer(handler).listen(8000, '127.0.0.1');
+```
+
+Client code:
+
+```javascript
+var Request = require('request');
+var Hawk = require('../lib/hawk');
+
+
+// Client credentials
+
+var credentials = {
+ id: 'dh37fgj492je',
+ key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
+ algorithm: 'hmac-sha-256'
+}
+
+// Send authenticated request
+
+var options = {
+ uri: 'http://127.0.0.1:8000/resource/1?b=1&a=2',
+ method: 'GET',
+ headers: {
+ authorization: Hawk.getAuthorizationHeader(credentials, 'GET', '/resource/1?b=1&a=2', '127.0.0.1', 8000, 'some-app-data')
+ }
+};
+
+Request(options, function (error, response, body) {
+
+ console.log(response.statusCode + ': ' + body);
+});
+```
View
@@ -0,0 +1,70 @@
+// Load modules
+
+var Http = require('http');
+var Request = require('request');
+var Hawk = require('../lib/hawk');
+
+
+// Declare internals
+
+var internals = {
+ credentials: {
+ dh37fgj492je: {
+ id: 'dh37fgj492je', // Required by Hawk.getAuthorizationHeader
+ key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
+ algorithm: 'hmac-sha-256',
+ user: 'Steve'
+ }
+ }
+};
+
+
+// Credentials lookup function
+
+var credentialsFunc = function (id, callback) {
+
+ return callback(null, internals.credentials[id]);
+};
+
+
+// Create HTTP server
+
+var handler = function (req, res) {
+
+ Hawk.authenticate(req, credentialsFunc, function (err, isAuthenticated, credentials, ext) {
+
+ res.writeHead(isAuthenticated ? 200 : 401, { 'Content-Type': 'text/plain' });
+ res.end(isAuthenticated ? 'Hello ' + credentials.user : 'Shoosh!');
+ });
+};
+
+Http.createServer(handler).listen(8000, '127.0.0.1');
+
+
+// Send unauthenticated request
+
+Request('http://127.0.0.1:8000/resource/1?b=1&a=2', function (error, response, body) {
+
+ console.log(response.statusCode + ': ' + body);
+});
+
+
+// Send authenticated request
+
+var options = {
+ uri: 'http://127.0.0.1:8000/resource/1?b=1&a=2',
+ method: 'GET',
+ headers: {
+ authorization: Hawk.getAuthorizationHeader(internals.credentials.dh37fgj492je, 'GET', '/resource/1?b=1&a=2', '127.0.0.1', 8000, 'some-app-data')
+ }
+};
+
+console.log(options.headers.authorization);
+
+Request(options, function (error, response, body) {
+
+ console.log(response.statusCode + ': ' + body);
+ process.exit(0);
+});
+
+
View
@@ -47,18 +47,18 @@ var internals = {};
exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
- var callback = (arg1 ? arg2 : arg1);
+ var callback = (arg2 ? arg2 : arg1);
var options = (arg2 ? arg1 : {});
// Check required HTTP headers: host, authentication
var hostHeader = (options.hostHeader ? req.headers[options.hostHeader.toLowerCase()] : req.headers.host);
if (!hostHeader) {
- return callback(new Error('Missing Host header'), false, null);
+ return callback(new Error('Missing Host header'), false, null, null);
}
if (!req.headers.authorization) {
- return callback(new Error('Missing Authorization header'), false, null);
+ return callback(new Error('Missing Authorization header'), false, null, null);
}
// Parse HTTP Authorization header
@@ -68,7 +68,7 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
// Verify authentication scheme
if (attributes instanceof Error) {
- return callback(attributes, false, null);
+ return callback(attributes, false, null, null);
}
// Verify required header attributes
@@ -77,7 +77,7 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
!attributes.ts ||
!attributes.mac) {
- return callback(new Error('Missing attributes'), false, null);
+ return callback(new Error('Missing attributes'), false, null, attributes.ext);
}
// Obtain host and port information
@@ -89,7 +89,7 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
hostParts.length !== 3 ||
!hostParts[1]) {
- return callback(new Error('Bad Host header'), false, null);
+ return callback(new Error('Bad Host header'), false, null, attributes.ext);
}
var host = hostParts[1];
@@ -100,34 +100,34 @@ exports.authenticate = function (req, credentialsFunc, arg1, arg2) {
credentialsFunc(attributes.id, function (err, credentials) {
if (err) {
- return callback(err, false, credentials);
+ return callback(err, false, credentials, attributes.ext);
}
if (!credentials) {
- return callback(new Error('Missing credentials'), false, null);
+ return callback(new Error('Missing credentials'), false, null, attributes.ext);
}
if (!credentials.key ||
!credentials.algorithm) {
- return callback(new Error('Invalid credentials'), false, credentials);
+ return callback(new Error('Invalid credentials'), false, credentials, attributes.ext);
}
if (['hmac-sha-1', 'hmac-sha-256'].indexOf(credentials.algorithm) === -1) {
- return callback(new Error('Unknown algorithm'), false, credentials);
+ return callback(new Error('Unknown algorithm'), false, credentials, attributes.ext);
}
// Calculate MAC
var mac = exports.calculateMAC(credentials.key, credentials.algorithm, attributes.ts, req.method, req.url, host, port, attributes.ext);
if (mac !== attributes.mac) {
- return callback(new Error('Bad mac'), false, credentials);
+ return callback(new Error('Bad mac'), false, credentials, attributes.ext);
}
// Successful authentication
- return callback(null, true, credentials);
+ return callback(null, true, credentials, attributes.ext);
});
};
@@ -142,17 +142,17 @@ exports.getWWWAuthenticateHeader = function (message) {
// Calculate the request MAC
-exports.calculateMAC = function (key, algorithm, timestamp, method, URI, host, port, ext) {
+exports.calculateMAC = function (key, algorithm, timestamp, method, uri, host, port, ext) {
// Parse request URI
- var uri = URL.parse(URI);
+ var url = URL.parse(uri);
// Construct normalized req string
var normalized = timestamp + '\n' +
method.toUpperCase() + '\n' +
- uri.pathname + (uri.search || '') + '\n' +
+ url.pathname + (url.search || '') + '\n' +
host.toLowerCase() + '\n' +
port + '\n' +
(ext || '') + '\n';
View
@@ -1,7 +1,7 @@
{
"name": "hawk",
"description": "HTTP Hawk Authentication Scheme",
- "version": "0.0.2",
+ "version": "0.0.3",
"author": "Eran Hammer <eran@hueniverse.com> (http://hueniverse.com)",
"contributors": [],
"repository": "git://github.com/hueniverse/hawk",
@@ -17,6 +17,7 @@
},
"dependencies": {},
"devDependencies": {
+ "request": "2.11.x",
"mocha": "1.x.x",
"should": "1.x.x",
"chai": "1.2.x"
Oops, something went wrong.

0 comments on commit 3873162

Please sign in to comment.