hawk

HTTP Holder-Of-Key Authentication Scheme.

Latest Version: 8.0.0
hapi-family
Installation:

npm: npm install @hapi/hawk

yarn: yarn add @hapi/hawk

Module Status:
Version License Node Dependencies Travis End of Life
8.0.0
hapi helmet github logo
BSD 12 Dependency Status Build Status

Introduction

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:

  • simplify and improve HTTP authentication for services that are unwilling or unable to deploy TLS for all resources,
  • secure credentials against leakage (e.g., when the client uses some form of dynamic configuration to determine where
    to send an authenticated request), and
  • avoid the exposure of credentials sent to a malicious server over an unauthenticated secure channel due to client
    failure to validate the server's identity as part of its TLS handshake.

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.

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: 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:

  • Key identifier: dh37fgj492je
  • Key: werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn
  • Algorithm: hmac sha256
  • Hash: 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.

Usage Example

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();