HTTP Hawk Authentication Scheme
Hawk is a Node.js library implementing the HTTP Hawk Authentication Scheme, a robust mechanism for making authenticated HTTP requests with partial cryptographic verification. It uses a message authentication code (MAC) algorithm to cover the HTTP method, request URI, host, and optionally the request payload, providing an alternative to HTTP Digest access authentication. Developed by Mozilla, the package is currently at version 9.0.2. It is in a 'maintenance mode' where no new features are added, and only security-related bug fixes are applied, with v9.0.2 announced as the final release. Key differentiators include its focus on two-legged client-server authentication (not OAuth delegation) and its history of ownership by hueniverse, then @hapi, and now Mozilla.
Common errors
-
Error: Invalid credentials id
cause The ID provided in the Hawk Authorization header does not match any known credentials on the server.fixEnsure the client is sending the correct `id` in its Hawk credentials that the server's `credentialsLookup` function can successfully resolve. -
401 Authentication Required (WWW-Authenticate: Hawk)
cause The server failed to authenticate the incoming Hawk request, often due to an invalid MAC, expired timestamp, or incorrect nonce.fixCheck client-side clock synchronization, ensure credentials (id, key, algorithm) are correct, and verify that the request details (URI, method, payload) used for MAC generation precisely match the server's expectations. Look for 'mac' or 'timestamp' errors in server logs. -
TypeError: Cannot read properties of undefined (reading 'authenticate')
cause Occurs when trying to use `Hawk.Server.authenticate` or `Hawk.Client.header` in a CommonJS (`require`) environment where the main `hawk` export might not directly expose `Client` or `Server` in a nested manner, or if the imports are incorrect for ESM.fixFor ESM, use `import { Server, Client } from 'hawk';`. For CJS, ensure `const Hawk = require('hawk');` and then use `Hawk.Server.authenticate` or `Hawk.Client.header`. Alternatively, import specific modules like `require('hawk/server')` or `require('hawk/client')` if the package structure allows.
Warnings
- breaking Version 8.0.0 dropped support for Node.js versions older than 12 and Hapi framework versions older than 18. Ensure your environment meets these minimum requirements.
- breaking Version 7.1.0 removed browser exports. If you were using Hawk directly in a browser environment, this version will break your application. The library is primarily for server-side Node.js applications.
- deprecated The `hawk` library is in 'maintenance mode' and version 9.0.2 is explicitly stated as the 'final release'. No new features will be added, and only security-related bug fixes will be applied. Users should plan for eventual migration if active development or new features are required.
- gotcha The package underwent several ownership and npm package name changes (from `hueniverse/hawk` to `@hapi/hawk` to `mozilla/hawk` published as `hawk`). Be mindful of which package version and name you are installing and importing to avoid compatibility issues.
- gotcha Version 9.0.0 dropped the requirement for `@hapi/sntp` for time synchronization. While this removes an unmaintained dependency, applications that relied on `sntp` for clock skew management may need to implement an alternative time synchronization workaround if strict clock synchronization is critical.
Install
-
npm install hawk -
yarn add hawk -
pnpm add hawk
Imports
- Client
const Client = require('hawk').Client;import { Client } from 'hawk'; - Server
const Server = require('hawk').Server;import { Server } from 'hawk'; - authenticate
import { Server } from 'hawk'; Server.authenticate(...);import { authenticate } from 'hawk/server'; - header
import { Client } from 'hawk'; Client.header(...);import { header } from 'hawk/client';
Quickstart
import { Server, Client } from 'hawk';
import http from 'http';
const credentials = {
id: process.env.HAWK_ID ?? 'dh37fgj492je',
key: process.env.HAWK_KEY ?? 'werxhqb98rpaxn39848xrunpaw3489ruxnpa98w4rxn',
algorithm: 'sha256' as const,
};
const credentialsLookup = (id: string, callback: (err: Error | null, credentials?: typeof credentials) => void) => {
if (id === credentials.id) {
return callback(null, credentials);
}
callback(new Error('Invalid credentials id'));
};
const server = http.createServer(async (req, res) => {
if (req.url === '/auth-resource') {
try {
const authResult = await Server.authenticate(req, credentialsLookup, {});
console.log('Server authenticated:', authResult.credentials.id);
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Authenticated resource access', user: authResult.credentials.user }));
} catch (err: any) {
console.error('Server authentication failed:', err.message);
res.writeHead(401, { 'WWW-Authenticate': 'Hawk' });
res.end('Authentication Required');
}
} else {
res.writeHead(404);
res.end('Not Found');
}
});
server.listen(8000, '127.0.0.1', () => {
console.log('Server running at http://127.0.0.1:8000/');
// Client example
const requestOptions = {
host: '127.0.0.1',
port: 8000,
path: '/auth-resource',
method: 'GET',
headers: {},
};
const header = Client.header(requestOptions.path, requestOptions.method, { credentials });
requestOptions.headers = { ...requestOptions.headers, Authorization: header.field };
const clientReq = http.request(requestOptions, (clientRes) => {
let data = '';
clientRes.on('data', (chunk) => (data += chunk));
clientRes.on('end', () => {
console.log(`Client received status: ${clientRes.statusCode}`);
console.log(`Client received body: ${data}`);
});
});
clientReq.on('error', (e) => console.error(`Client request error: ${e.message}`));
clientReq.end();
});