dflydev/hawk
PHP implementation of the Hawk HTTP authentication scheme. Build a client via ClientBuilder, sign requests with MAC-based Authorization headers using credentials, URL and method, with optional payload/content-type, nonce and ext (plus Oz app/dlg support).
Installation:
composer require dflydev/hawk
First Use Case: Secure an API endpoint with Hawk authentication. Start by creating a client for signing requests and a server for validating them.
// app/Providers/AppServiceProvider.php
use Dflydev\Hawk\Client\ClientBuilder;
use Dflydev\Hawk\Server\ServerBuilder;
public function register()
{
$this->app->singleton('hawk.client', function () {
return ClientBuilder::create()->build();
});
$this->app->singleton('hawk.server', function () {
return ServerBuilder::create(function ($id) {
// Fetch credentials from your database or config
return new \Dflydev\Hawk\Credentials\Credentials(
config('hawk.shared_key'),
'sha256',
$id
);
})->build();
});
}
Configure Hawk in config/hawk.php:
return [
'shared_key' => env('HAWK_SHARED_KEY'),
'timestamp_skew_sec' => 60,
'localtime_offset_sec' => 0,
];
Signing Requests: Use the client to sign outgoing requests in middleware or services.
// app/Http/Middleware/SignRequest.php
public function handle($request, Closure $next)
{
$credentials = new \Dflydev\Hawk\Credentials\Credentials(
config('hawk.shared_key'),
'sha256',
auth()->id()
);
$client = app('hawk.client');
$requestObj = $client->createRequest(
$credentials,
$request->fullUrl(),
$request->method(),
[
'payload' => $request->getContent(),
'content_type' => $request->header('Content-Type'),
]
);
$request->headers->set(
$requestObj->header()->fieldName(),
$requestObj->header()->fieldValue()
);
return $next($request);
}
Authenticating Responses: Verify server responses in middleware or controllers.
// app/Http/Middleware/VerifyResponse.php
public function handle($request, Closure $next)
{
$response = $next($request);
if ($response->headers->has('Server-Authorization')) {
$credentials = new \Dflydev\Hawk\Credentials\Credentials(
config('hawk.shared_key'),
'sha256',
auth()->id()
);
$client = app('hawk.client');
$isAuthenticated = $client->authenticate(
$credentials,
$requestObj, // Store this from the request phase
$response->headers->get('Server-Authorization'),
[
'payload' => $response->getContent(),
'content_type' => $response->header('Content-Type'),
]
);
if (!$isAuthenticated) {
abort(403, 'Unauthorized response');
}
}
return $response;
}
Validating Requests: Use the server to validate incoming requests in middleware or controllers.
// app/Http/Middleware/ValidateHawk.php
public function handle($request, Closure $next)
{
$server = app('hawk.server');
try {
$response = $server->authenticate(
$request->method(),
parse_url($request->fullUrl(), PHP_URL_HOST),
parse_url($request->fullUrl(), PHP_URL_PORT) ?: 80,
parse_url($request->fullUrl(), PHP_URL_PATH) . '?' . $request->query->__toString(),
$request->header('Content-Type'),
$request->getContent(),
$request->header('Authorization')
);
// Attach credentials to the request for later use
$request->setHawkCredentials($response->credentials());
$request->setHawkArtifacts($response->artifacts());
} catch (\Dflydev\Hawk\Server\UnauthorizedException $e) {
abort(401, 'Unauthorized');
}
return $next($request);
}
Signing Responses: Sign server responses in controllers or middleware.
// app/Http/Controllers/ApiController.php
public function show(Request $request)
{
$server = app('hawk.server');
$credentials = $request->hawkCredentials;
$artifacts = $request->hawkArtifacts;
$header = $server->createHeader($credentials, $artifacts, [
'payload' => $this->getResponsePayload(),
'content_type' => 'application/json',
]);
return response()->json($this->getResponsePayload())
->header($header->fieldName(), $header->fieldValue());
}
Leverage Laravel's Service Container: Bind Hawk client/server instances to the container for easy access.
Use Middleware for Centralized Logic: Place Hawk logic in middleware to avoid repetition across controllers.
Store Credentials Securely:
Use Laravel's env() or config() to manage shared keys securely.
Extend Request/Response Objects:
Add Hawk-specific methods to Laravel's Request and Response classes for seamless integration.
// app/Http/Requests/HawkRequest.php
trait HawkRequest
{
public function hawkCredentials()
{
return $this->attributes->get('hawk.credentials');
}
public function setHawkCredentials($credentials)
{
$this->attributes->set('hawk.credentials', $credentials);
}
// Similar methods for artifacts
}
Nonce Management:
Cache facade to store nonces.// app/Providers/AppServiceProvider.php
$this->app->singleton('hawk.nonce_provider', function () {
return new class {
public function generate()
{
return Str::random(32);
}
public function validate($nonce)
{
return Cache::has("hawk.nonce.$nonce") ? false : Cache::put("hawk.nonce.$nonce", true, now()->addMinutes(5));
}
};
});
Timestamp Skew:
timestamp_skew_sec to allow for minor clock differences between client and server.Payload Hashing:
content_type is correctly set when calculating payload hashes.application/octet-stream.Bewit Usage:
GET and HEAD requests.Error Handling:
UnauthorizedException on the server side to return appropriate HTTP status codes (e.g., 401).Log Hawk Headers:
Log the Authorization and Server-Authorization headers for debugging.
\Log::debug('Hawk Authorization', [
'header' => $request->header('Authorization'),
'server_header' => $response->headers->get('Server-Authorization'),
]);
Validate Credentials: Double-check that credentials (shared key, algorithm, ID) match between client and server.
Time Synchronization: Ensure client and server clocks are synchronized. Use NTP or a time synchronization service if needed.
Payload Mismatches:
Verify that the payload and content_type used in signing match those sent/received.
Custom Crypto:
Extend Dflydev\Hawk\Crypto\Crypto to support additional algorithms or modify MAC calculation logic.
Nonce Providers: Implement custom nonce providers for specific use cases (e.g., UUID-based nonces).
Credentials Providers:
Use Laravel's Auth system to dynamically fetch credentials.
$credentialsProvider = function ($id) {
$user = User::find($id);
return new \Dflydev\Hawk\Credentials\Credentials(
$user->hawk_shared_key,
'sha256',
$user->id
);
};
Artifacts Handling:
Extend Dflydev\Hawk\Crypto\Artifacts to include custom metadata in Hawk headers.
Middleware Chaining: Combine Hawk middleware with Laravel's built-in middleware (e.g.,
How can I help you explore Laravel packages today?