Hawk — A PHP Implementation

Overview

Hawk — A PHP Implementation

Hawk is an HTTP authentication scheme using a message authentication code (MAC) algorithm to provide partial HTTP request cryptographic verification. — hawk README

Installation

Through Composer as dflydev/hawk.

Client

Building a Client

The Client has a few required dependencies. It is generally easier to construct a Client by using the ClientBuilder. A Client can be built without setting anything to get sane defaults.

Simple ClientBuilder Example

<?php

// Simple example
$client = Dflydev\Hawk\Client\ClientBuilder::create()
    ->build()

Complete ClientBuilderExample

<?php

// A complete example
$client = Dflydev\Hawk\Client\ClientBuilder::create()
    ->setCrypto($crypto)
    ->setTimeProvider($timeProvider)
    ->setNonceProvider($nonceProvider)
    ->setLocaltimeOffset($localtimeOffset)
    ->build()

Creating a Request

In order for a client to be able to sign a request, it needs to know the credentials for the user making the request, the URL, method, and optionally payload and content type of the request.

All available options include:

  • payload: The body of the request
  • content_type: The content-type for the request
  • nonce: If a specific nonce should be used in favor of one being generated automatically by the nonce provider.
  • ext: An ext value specific for this request
  • app: The app for this request (Oz specific)
  • dlg: The delegated-by value for this request (Oz specific)

Create Request Example

<?php

$request = $client->createRequest(
    $credentials,
    'http://example.com/foo/bar?whatever',
    'POST',
    array(
        'payload' => 'hello world!',
        'content_type' => 'text/plain',
    )
);

// Assuming a hypothetical $headers object that can be used to add new headers
// to an outbound request, we can add the resulting 'Authorization' header
// for this Hawk request by doing:
$headers->set(
    $request->header()->fieldName(), // 'Authorization'
    $request->header()->fieldValue() // 'Hawk id="12345", mac="ad8c9f', ...'
);

The Client Request Object

The Request represents everything the client needs to know about a request including a header and the artifacts that were used to create the request.

  • header(): A Header instance that represents the request
  • artifacts(): An Artifacts instance that contains the values that were used in creating the request

The header is required to be able to get the properly formatted Hawk authorization header to send to the server. The artifacts are useful in the case that authentication will be done on the server response.

Authenticate Server Response

Hawk provides the ability for the client to authenticate a server response to ensure that the response sent back is from the intended target.

All available options include:

  • payload: The body of the response
  • content_type: The content-type for the response

Authenticate Response Example

<?php

// Assuming a hypothetical $headers object that can be used to get headers sent
// back as the response of a user agent request, we can get the value for the
// 'Server-Authorization' header.
$header = $headers->get('Server-Authorization');

// We need to use the original credentials, the original request, the value
// for the 'Server-Authorization' header, and optionally the payload and
// content type of the response from the server.
$isAuthenticatedResponse = $client->authenticate(
    $credentials,
    $request,
    $header,
    array(
        'payload' => '{"message": "good day, sir!"}',
        'content_type' => 'application/json',
    )
);

Complete Client Example

<?php

// Create a set of Hawk credentials
$credentials = new Dflydev\Hawk\Credentials\Credentials(
    'afe89a3x',  // shared key
    'sha256',    // default: sha256
    '12345'      // identifier, default: null
);

// Create a Hawk client
$client = Dflydev\Hawk\Client\ClientBuilder::create()
    ->build();

// Create a Hawk request based on making a POST request to a specific URL
// using a specific user's credentials. Also, we're expecting that we'll
// be sending a payload of 'hello world!' with a content-type of 'text/plain'.
$request = $client->createRequest(
    $credentials,
    'http://example.com/foo/bar?whatever',
    'POST',
    array(
        'payload' => 'hello world!',
        'content_type' => 'text/plain',
    )
);

// Ask a really useful fictional user agent to make a request; note that the
// request we are making here matches the details that we told the Hawk client
// about our request.
$response = Fictional\UserAgent::makeRequest(
    'POST',
    'http://example.com/foo/bar?whatever',
    array(
        'content_type' => 'text/plain',
        $request->header()->fieldName() => $request->header()->fieldValue(),
    ),
    'hello world!'
);

// This part is optional but recommended! At this point if we have a successful
// response we could just look at the content and be done with it. However, we
// are given the tools to authenticate the response to ensure that the response
// we were given came from the server we were expecting to be talking to.
$isAuthenticatedResponse = $client->authenticate(
    $credentials,
    $request,
    $response->headers->get('Server-Authorization'),
    array(
        'payload' => $response->getContent(),
        'content_type' => $response->headers->get('content-type'),
    )
);

if (!$isAuthenticatedResponse) {
    die("The server did a very bad thing...");
}

// Huzzah!

Bewit

Hawk supports a method for granting third-parties temporary access to individual resources using a query parameter called bewit.

The return value is a string that represents the bewit. This string should be added to a requested URI by appending it to the end of the URI. If the URI has query parameters already, the bewit should have &bewit= appended to the front of it. If the URI does not have query parameters already, the bewit should have ?bewit= appended to the front of it.

Client Bewit Example

<?php

$bewit = $client->createBewit(
    $credentials,
    'https://example.com/posts?foo=bar',
    300 // ttl in seconds
);

Server

Building a Server

The Server has a few required dependencies. It is generally easier to construct a Server by using the ServerBuilder. A Server can be built without setting anything but the credentials provider to get sane defaults.

Simple ServerBuilder Example

<?php

$credentialsProvider = function ($id) {
    if ('12345' === $id) {
        return new Dflydev\Hawk\Credentials\Credentials(
            'afe89a3x',  // shared key
            'sha256',    // default: sha256
            '12345'      // identifier, default: null
        );
    }
};

// Simple example
$server = Dflydev\Hawk\Server\ServerBuilder::create($credentialsProvider)
    ->build()

Complete ServerBuilderExample

<?php

$credentialsProvider = function ($id) {
    if ('12345' === $id) {
        return new Dflydev\Hawk\Credentials\Credentials(
            'afe89a3x',  // shared key
            'sha256',    // default: sha256
            '12345'      // identifier, default: null
        );
    }
};

// A complete example
$server = Dflydev\Hawk\Server\ServerBuilder::create($credentialsProvider)
    ->setCrypto($crypto)
    ->setTimeProvider($timeProvider)
    ->setNonceValidator($nonceValidator)
    ->setTimestampSkewSec($timestampSkewSec)
    ->setLocaltimeOffsetSec($localtimeOffsetSec)
    ->build()

Authenticating a Request

In order for a server to be able to authenticate a request, it needs to be able to build the same MAC that the client did. It does this by getting the same information about the request that the client knew about when it signed the request.

In particular, the authorization header should include the ID. This ID is used to retrieve the credentials (notably the key) in order to calculate the MAC based on the rest of the request information.

Authenticate Example

<?php

// Get the authorization header for the request; it should be in the form
// of 'Hawk id="...", mac="...", [...]'
$authorization = $headers->get('Authorization');

try {
    $response = $server->authenticate(
        'POST',
        'example.com',
        80,
        '/foo/bar?whatever',
        'text/plain',
        'hello world!'
        $authorization
    );
} catch(Dflydev\Hawk\Server\UnauthorizedException $e) {
    // If authorization is incorrect (invalid mac, etc.) we can catch an
    // unauthorized exception.
    throw $e;
}

// The credentials associated with this request. This is where one could access
// the ID for the user that made this request.
$credentials = $response->credentials();

// The artifacts associated with this request. This is where one could access
// things like the 'ext', 'app', and 'dlg' values sent with the request.
$artifacts = $response->artifacts();

The Server Response Object

The Response represents everything the server needs to know about a request including the credentials and artifacts that are associated with the request.

  • credentials()
  • artifacts()

Creating a Response Header

Hawk provides the ability for the server to sign the response to provide the client with a way to authenticate a server response.

All available options include:

  • payload: The body of the request
  • content_type: The content-type for the request
  • ext: An ext value specific for this request

Create Response Header Example

<?php

// Using the same credentials and artifacts from the server authenticate
// response, we can create a 'Server-Authorization' header.
$header = $server->createHeader($credentials, $artifacts, array(
    'payload' => '{"message": "good day, sir!"}',
    'content_type' => 'application/json',
));

// Set the header using PHP's header() function.
header(sprintf("%s: %s", $header->fieldName(), $header->fieldValue()));

Complete Server Example

<?php

// Create a simple credentials provider
$credentialsProvider = function ($id) {
    if ('12345' === $id) {
        return new Dflydev\Hawk\Credentials\Credentials(
            'afe89a3x',  // shared key
            'sha256',    // default: sha256
            '12345'      // identifier, default: null
        );
    }
};

// Create a Hawk server
$server = Dflydev\Hawk\Server\ServerBuilder::create($credentialsProvider)
    ->build()

// Get the authorization header for the request; it should be in the form
// of 'Hawk id="...", mac="...", [...]'
$authorization = $headers->get('Authorization');

try {
    $response = $server->authenticate(
        'POST',
        'example.com',
        80,
        '/foo/bar?whatever',
        'text/plain',
        'hello world!'
        $authorization
    );
} catch(Dflydev\Hawk\Server\UnauthorizedException $e) {
    // If authorization is incorrect (invalid mac, etc.) we can catch an
    // unauthorized exception.
    throw $e;
}

// Huzzah! Do something at this point with the request as we now know that
// it is an authenticated Hawk request.
//
// ...
//
// Ok we are done doing things! Assume based on what we did we ended up deciding
// the following payload and content type should be used:

$payload = '{"message": "good day, sir!"}';
$contentType = 'application/json';

// Create a Hawk header to sign our response
$header = $server->createHeader($credentials, $artifacts, array(
    'payload' => $payload,
    'content_type' => $contentType,
));

// Send some headers
header(sprintf("%s: %s", 'Content-Type', 'application/json'));
header(sprintf("%s: %s", $header->fieldName(), $header->fieldValue()));

// Output our payload
print $payload;

Bewit

Hawk supports a method for granting third-parties temporary access to individual resources using a query parameter called bewit.

Bewit authentication should only occur for GET and HEAD requests. The return value of an authenticated bewit is a Server Response object.

Server Bewit Example

<?php

$response = $server->authenticateBewit(
    'example.com',
    443,
    '/posts?bewit=ZXhxYlpXdHlrRlpJaDJEN2NYaTlkQVwxMzY4OTk2ODAwXE8wbWhwcmdvWHFGNDhEbHc1RldBV3ZWUUlwZ0dZc3FzWDc2dHBvNkt5cUk9XA'
);

Crypto

Dflydev\Hawk\Crypto\Crypto

Tools for calculation of and comparison of MAC values.

  • calculatePayloadHash($payload, $algorithm, $contentType)
  • calculateMac($type, CredentialsInterface $credentials, Artifacts $attributes)
  • calculateTsMac($ts, CredentialsInterface $credentials)
  • fixedTimeComparison($a, $b)
    Used to ensure that the comparing two strings will always take the same amount of time regardless of whether they are the same or not.

Dflydev\Hawk\Crypto\Artifacts

A container for all of the pieces of data that may go into the creation of a MAC.

Credentials

Dflydev\Hawk\Credentials\CredentialsInterface

Represents a valid set of credentials.

  • key(): Used to calculate the MAC
  • algorithm(): The algorithm used to calculate hashes
  • id(): An identifier (e.g. username) for whom the key belongs

In some contexts only the key may be known.

Dflydev\Hawk\Credentials\Credentials

A simple implementation of CredentialsInterface.

<?php

$credentials = new Dflydev\Hawk\Credentials\Credentials(
    $key,        // shared key
    $algorithm,  // default: sha256
    $id          // identifier, default: null
);

Header

Dflydev\Hawk\Header\Header

  • fieldName(): The name for the header field
  • fieldValue(): The value for the header field
  • attributes(): The attributes used to build the field value

Dflydev\Hawk\Header\HeaderFactory

  • create($fieldName, array $attributes = null)
    Creates a Hawk header for a given field name for a set of attributes.

  • createFromString($fieldName, $fieldValue, array $requiredKeys = null)
    Creates a Hawk header for a given field name from a Hawk value string. For example, 'Hawk id="foo", mac="1234"' would be an example of a Hawk value string. This is useful for converting a header value coming in off the wire.

    Throws:

    • Dflydev\Hawk\Header\FieldValueParserException
    • Dflydev\Hawk\Header\NotHawkAuthorizationException

Dflydev\Hawk\Header\HeaderParser

  • parseFieldValue($fieldValue, array $requiredKeys = null)
    Parses a field value string into an associative array of attributes.

    Throws:

    • Dflydev\Hawk\Header\FieldValueParserException
    • Dflydev\Hawk\Header\NotHawkAuthorizationException

Dflydev\Hawk\Header\FieldValueParserException

Indicates that a string claims to be a Hawk string but it cannot be completely parsed. This is mostly a sign of a corrupted or malformed header value.

Dflydev\Hawk\Header\NotHawkAuthorizationException

Indicates that the string has nothing to do with Hawk. Currently means that the string does not start with 'Hawk'.

License

MIT, see LICENSE.

Community

If you have questions or want to help out, join us in #dflydev on irc.freenode.net.

Comments
  • Overhaul

    Overhaul

    New usage looks like:

    <?php
    
    require_once __DIR__.'/vendor/autoload.php';
    
    $crypto = new Dflydev\Hawk\Crypto\Crypto;
    
    $client = Dflydev\Hawk\Client\ClientBuilder::create()
        ->setCrypto($crypto)
        ->build();
    
    $credentials = new Dflydev\Hawk\Credentials\Credentials('asdf', 'sha256', '1234');
    
    $request = $clientRequest = $client->createRequest(
        $credentials,
        'http://srcmvn.com/foo/bar?whatever',
        'GET',
        array(
            'payload' => 'hello world!',
            'content_type' => 'text/plain',
        )
    );
    
    $credentialsCallback = function ($id) use ($credentials) {
        if ($credentials->id() === $id) {
            return new Dflydev\Hawk\Credentials\Credentials(
                $credentials->key(),
                $credentials->algorithm(),
                $credentials->id()
            );
        }
    };
    
    $server = Dflydev\Hawk\Server\ServerBuilder::create($credentialsCallback)
        ->setCrypto($crypto)
        //->setTimestampSkewSec(1)
        ->build();
    
    //sleep(2);
    
    $request = $server->createRequest(
        'GET',
        'srcmvn.com',
        80,
        '/foo/bar?whatever',
        'text/plain',
        'hello world!',
        $request->header()->fieldValue()
    );
    
    try {
        list ($credentials, $artifacts) = $server->authenticate($request);
    
        $header = $server->createHeader($credentials, $artifacts, array(
            'payload' => '{"message": "good day, sir!"}',
            'content_type' => 'application/json',
        ));
    } catch (Dflydev\Hawk\Server\UnauthorizedException $e) {
        print_r($e);
    
        throw $e;
    }
    
    // Authenticate the response w/ the payload.
    // Uses credentials and original client request
    $authenticatedResponse = $client->authenticateResponse(
        $credentials,
        $clientRequest,
        $header->fieldValue(),
        array(
            'payload' => '{"message": "good day, sir!"}',
            'content_type' => 'application/json',
        )
    );
    
    if ($authenticatedResponse) {
        print " [ AUTHENTICATED RESPONSE! ]\n";
    } else {
        print " [ NOT authenticated... :( ]\n";
    }
    
    // Authenticate the response w/o the payload.
    $authenticatedResponse = $client->authenticateResponse(
        $credentials,
        $clientRequest,
        $header->fieldValue()
    );
    
    if ($authenticatedResponse) {
        print " [ AUTHENTICATED RESPONSE! ]\n";
    } else {
        print " [ NOT authenticated... :( ]\n";
    }
    
    opened by simensen 4
  • How do include this in composer json file

    How do include this in composer json file

    In my composer json file i have the following:

    {
        "require": {
            "dflydev/hawk"
        }
    }
    

    But its giving be an error. How should this be done?

    opened by patchthecode 2
  • Scrutinizer Auto-Fixes

    Scrutinizer Auto-Fixes

    @simensen requested this pull request.

    It consists of patches automatically generated for this project on Scrutinizer: https://scrutinizer-ci.com/g/dflydev/dflydev-hawk/

    opened by scrutinizer-auto-fixer 0
  • Update example to throw an unauthorized exception on invalid credentials

    Update example to throw an unauthorized exception on invalid credentials

    Server expects that credentials provider will always gives an Cretaintails object. Therefore throw unauthorized exception if no matching credeaintils were found.

    Update readme so that it will notice to the user.

    opened by sadika9 0
  • Server does not tolerate attributes string without spaces

    Server does not tolerate attributes string without spaces

    Exploding on ', ' requires a space in the attribute string (https://github.com/dflydev/dflydev-hawk/tree/master/src/Dflydev/Hawk/Header/HeaderParser.php#L15) which deviates from the regex used in the official HAWK implementation (https://github.com/hueniverse/hawk/blob/master/lib/utils.js#L125).

    Maybe switch to splitting on , and doing a trim()?

    opened by zackangelo 0
  • Server usage

    Server usage

    From the server side, it seems that not only are we authenticating the user's credentials, but also authenticating against their request parameters.

    For example, on the server usage it you're asking for 'POST' method, domain, api path, content-type, and even a specific post payload.

    I'm guessing this is part of the Hawk protocol.

    But doesn't this mean, that we have to manually write what kind of request the user is going to send, for every possible API end point? Can those extra request parameters be optional?

    opened by CMCDragonkai 8
Owner
dflydev
I'm Dragonfly Development (aka dflydev), a technology strategy consultancy created by Beau Simensen (@simensen). I also host Beau's public open-source projects.
dflydev
PHP implementation of openid connect-core

OIDC Discovery PHP implementation of https://openid.net/specs/openid-connect-core-1_0.html Install Via Composer $ composer require digitalcz/openid-co

DigitalCz 3 Dec 14, 2022
A Laravel 5 package for OAuth Social Login/Register implementation using Laravel socialite and (optionally) AdminLTE Laravel package

laravel-social A Laravel 5 package for OAuth Social Login/Register implementation using Laravel socialite and (optionally) AdminLTE Laravel package. I

Sergi Tur Badenas 42 Nov 29, 2022
OAuth server implementation for WP API

WP REST API - OAuth 1.0a Server Connect applications to your WordPress site without ever giving away your password. This plugin uses the OAuth 1.0a pr

WordPress REST API Team 314 Dec 10, 2022
php database agnostic authentication library for php developers

Whoo Whoo is a database agnostic authentication library to manage authentication operation easily. Whoo provides you a layer to access and manage user

Yunus Emre Bulut 9 Jan 15, 2022
Single file PHP that can serve as a JWT based authentication provider to the PHP-CRUD-API project

Single file PHP that can serve as a JWT based authentication provider to the PHP-CRUD-API project

Maurits van der Schee 163 Dec 18, 2022
OAuth 1/2 Provider implementations for chillerlan/php-oauth-core. PHP 7.4+

chillerlan/php-oauth-providers Documentation See the wiki for advanced documentation. Requirements PHP 7.4+ a PSR-18 compatible HTTP client library of

chillerlan 4 Dec 2, 2022
A Native PHP MVC With Auth. If you will build your own PHP project in MVC with router and Auth, you can clone this ready to use MVC pattern repo.

If you will build your own PHP project in MVC with router and Auth, you can clone this ready to use MVC pattern repo. Auth system is implemented. Works with bootstrap 5. Composer with autoload are implemented too for future composer require.

null 2 Jun 6, 2022
A spec compliant, secure by default PHP OAuth 2.0 Server

PHP OAuth 2.0 Server league/oauth2-server is a standards compliant implementation of an OAuth 2.0 authorization server written in PHP which makes work

The League of Extraordinary Packages 6.2k Jan 4, 2023
Open source social sign on PHP Library. HybridAuth goal is to act as an abstract api between your application and various social apis and identities providers such as Facebook, Twitter and Google.

Hybridauth 3.7.1 Hybridauth enables developers to easily build social applications and tools to engage websites visitors and customers on a social lev

hybridauth 3.3k Dec 23, 2022
Multi-provider authentication framework for PHP

Opauth is a multi-provider authentication framework for PHP, inspired by OmniAuth for Ruby. Opauth enables PHP applications to do user authentication

Opauth – PHP Auth Framework 1.7k Jan 1, 2023
PHP 5.3+ oAuth 1/2 Client Library

PHPoAuthLib NOTE: I'm looking for someone who could help to maintain this package alongside me, just because I don't have a ton of time to devote to i

David Desberg 1.1k Dec 27, 2022
A flexible, driver based Acl package for PHP 5.4+

Lock - Acl for PHP 5.4+ I'm sad to say that Lock is currently not maintained. I won't be able to offer support or accept new contributions for the cur

Beatswitch 892 Dec 30, 2022
PHP library for Two Factor Authentication (TFA / 2FA)

PHP library for Two Factor Authentication PHP library for two-factor (or multi-factor) authentication using TOTP and QR-codes. Inspired by, based on b

Rob Janssen 896 Dec 30, 2022
:atom: Social (OAuth1\OAuth2\OpenID\OpenIDConnect) sign with PHP :shipit:

SocialConnect Auth Getting Started :: Documentation :: Demo Open source social sign on PHP. Connect your application(s) with social network(s). Code e

SocialConnect 518 Dec 28, 2022
documentation for the oauth2-server-php library

OAuth2 Server PHP Documentation This repository hosts the documentation for the oauth2-server-php library. All submissions are welcome! To submit a ch

Brent Shaffer 227 Nov 24, 2022
The first PHP Library to support OAuth for Twitter's REST API.

THIS IS AN MODIFIED VERSION OF ABRAHAMS TWITTER OAUTH CLASS The directories are structured and the class uses PHP5.3 namespaces. Api.php has a new

Ruud Kamphuis 51 Feb 11, 2021
PHP library to verify and validate Apple IdentityToken and authenticate a user with Apple ID.

Sign-in with Apple SDK Installation Recommended and easiest way to installing library is through Composer. composer require azimolabs/apple-sign-in-ph

Azimo Labs 79 Nov 8, 2022
:atom: Social (OAuth1\OAuth2\OpenID\OpenIDConnect) sign with PHP :shipit:

SocialConnect Auth Getting Started :: Documentation :: Demo Open source social sign on PHP. Connect your application(s) with social network(s). Code e

SocialConnect 458 Apr 1, 2021
PHP package built for Laravel 5.* to easily handle a user email verification and validate the email

jrean/laravel-user-verification is a PHP package built for Laravel 5.* & 6.* & 7.* & 8.* to easily handle a user verification and validate the e-mail.

Jean Ragouin 802 Dec 29, 2022