HTTP Holder-Of-Key Authentication Scheme.
Hawk is an HTTP authentication scheme providing mechanisms for making authenticated HTTP requests with
partial cryptographic verification of the request and response, covering the HTTP method, request URI, host,
and optionally the request payload.
Similar to the HTTP Digest access authentication schemes, Hawk uses a set of
client credentials which include an identifier (e.g. username) and key (e.g. password). Likewise, just as with the Digest scheme,
the key is never included in authenticated requests. Instead, it is used to calculate a request MAC value which is
included in its place.
However, Hawk has several differences from Digest. In particular, while both use a nonce to limit the possibility of
replay attacks, in Hawk the client generates the nonce and uses it in combination with a timestamp, leading to less
"chattiness" (interaction with the server).
Also unlike Digest, this scheme is not intended to protect the key itself (the password in Digest) because
the client and server must both have access to the key material in the clear.
The primary design goals of this scheme are to:
In addition, Hawk supports a method for granting third-parties temporary access to individual resources using
a query parameter called bewit (in falconry, a leather strap used to attach a tracking device to the leg of a hawk).
The Hawk scheme requires the establishment of a shared symmetric key between the client and the server,
which is beyond the scope of this module. Typically, the shared credentials are established via an initial
TLS-protected phase or derived from some other shared confidential information available to both the client
and the server.
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: example.com:8000
The resource server returns an 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:
dh37fgj492je
werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn
hmac sha256
6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=
The client generates the authentication header by calculating a timestamp (e.g. the number of seconds since January 1, 1970 00:00:00 GMT), generating a nonce, and constructing the normalized request string (each value followed by a newline character):
hawk.1.header
1353832234
j4h3g2
GET
/resource/1?b=1&a=2
example.com
8000
some-app-ext-data
The request MAC is calculated using HMAC with the specified hash algorithm "sha256" and the key over the normalized request string.
The result is base64-encoded to produce the request MAC:
6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE=
The client includes the Hawk key identifier, timestamp, nonce, application specific data, and request MAC with the request using
the HTTP Authorization request header field:
GET /resource/1?b=1&a=2 HTTP/1.1
Host: example.com:8000
Authorization: Hawk id="dh37fgj492je", ts="1353832234", nonce="j4h3g2", ext="some-app-ext-data", mac="6R4rV5iE+NPoym+WwjeHzjAGXUtLNIxmo1vpMofpLAE="
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.
Server code:
const Http = require('http');
const Hawk = require('@hapi/hawk');
// Credentials lookup function
const credentialsFunc = function (id) {
const credentials = {
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256',
user: 'Steve'
};
return credentials;
};
// Create HTTP server
const handler = async function (req, res) {
let payload, status;
// Authenticate incoming request
try {
const { credentials, artifacts } = await Hawk.server.authenticate(req, credentialsFunc);
payload = `Hello ${credentials.user} ${artifacts.ext}`;
status = 200;
} catch (error) {
payload = 'Shoosh!';
status = 401;
}
// Prepare response
const headers = { 'Content-Type': 'text/plain' };
// Generate Server-Authorization response header
const header = Hawk.server.header(credentials, artifacts, { payload, contentType: headers['Content-Type'] });
headers['Server-Authorization'] = header;
// Send the response back
res.writeHead(status, headers);
res.end(payload);
};
// Start server
Http.createServer(handler).listen(8000, 'example.com');Client code:
const Request = require('request');
const Hawk = require('@hapi/hawk');
// Client credentials
const credentials = {
id: 'dh37fgj492je',
key: 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256'
}
// Request options
const requestOptions = {
uri: 'http://example.com:8000/resource/1?b=1&a=2',
method: 'GET',
headers: {}
};
// Generate Authorization request header
const { header } = Hawk.client.header('http://example.com:8000/resource/1?b=1&a=2', 'GET', { credentials: credentials, ext: 'some-app-data' });
requestOptions.headers.Authorization = header;
// Send authenticated request
Request(requestOptions, function (error, response, body) {
// Authenticate the server's response
const isValid = Hawk.client.authenticate(response, credentials, header.artifacts, { payload: body });
// Output results
console.log(`${response.statusCode}: ${body}` + (isValid ? ' (valid)' : ' (invalid)'));
});Hawk utilized the SNTP module for time sync management. By default, the local machine time is used. To automatically retrieve and synchronize the clock within the application, use the SNTP 'start()' method.
Hawk.sntp.start();