Authentication

equip/auth is an optional middleware for implementing authentication that integrates with Equip.

Authentication Handler

AuthHandler is the middleware class that coordinates the authentication process. See Equip documentation for how to add middleware to an application.

The constructor for AuthHandler takes four parameters, which are discussed in the next few sections and should be configured in the injector.

Token Extractor

The middleware checks the ServerRequestInterface instance representing the application request for an existing authentication token. To do this, it must know how to extract that token from that request.

This method of extraction is represented by an implementation of Token\ExtractorInterface, which is the first parameter passed to the AuthHandler constructor.

These token extractor implementations are bundled with this library:

  • HeaderExtractor extracts the token from a request header. Its constructor takes the header name.
  • QueryExtractor extracts the token from a query string parameter taken from the request URI. Its constructor takes the name of the parameter.

The injector can be configured to use a specific extractor implementation like so:

use Equip\Auth\Token\ExtractorInterface;
use Equip\Auth\Token\QueryExtractor;

$injector->alias(
    ExtractInterface::class,
    QueryExtractor::class
);
$injector->define(
    QueryExtractor::class,
    [':parameter' => 'al']
);

Credentials Extractor

If no authentication token is present in the request, the middleware then checks for credentials representing a user to authenticate. As with tokens, it must know how to extract these credentials from the ServerRequestInterface instance.

This method of extraction is represented by an implementation of Credentials\ExtractorInterface, which is the second parameter passed to the AuthHandler constructor.

These credentials extractor implementations are bundled with this library:

  • BodyExtractor extracts the credentials from top-level properties of the parsed request body. Its constructor takes the names of the properties containing the user identifier and password.

The injector can be configured to use a specific extractor implementation like so:

use Equip\Auth\Credentials\ExtractorInterface;
use Equip\Auth\Credentials\BodyExtractor;

$injector->alias(
    ExtractorInterface::class,
    BodyExtractor::class
);

NOTE: When using BodyExtractor it is expected that any *ContentHandler middleware will be placed before AuthHandler to ensure that the request body has been parsed before authentication is attempted.

Adapter

If the middleware does not find either an authentication token nor user credentials in the request, it will handle throwing an instance of UnauthorizedException. If it does find either one, it must know how to validate them, i.e. verify that the authentication token exists and has not expired or that the credentials represent an existing user.

This method of validation is represented by an implementation of AdapterInterface, which is the third parameter passed to the AuthHandler constructor.

This library presently contains no bundled implementations. This is due in part to the number of potential implementations based on factors such as varying persistent stores used for tokens and user credentials, password hashing algorithms, etc.

It is possible that implementations will be added for common use cases in the future. Until then, it is recommended that you create an implementation of this interface specific to your use case.

Request Filter

The middleware may need to skip authentication altogether depending on the request. A common use case for this is requests with the OPTIONS method, which are used for implementing CORS.

The check for determining whether authentication should happen is represented by an implementation of RequestFilterInterface, which is the fourth parameter passed to the AuthHandler constructor. This parameter is optional; if no value is specified, authentication will happen for all requests.

This library presently contains no bundled implementations. It is possible that implementations will be added for common use cases in the future. Until then, it is recommended that you create an implementation of this interface specific to your use case.

Writing Custom Adapters

AdapterInterface contains two methods that its implementations must include.

validateToken() accepts a string representing an authentication token extracted from the application request. It is the responsibility of the adapter to handle any necessary decoding of token.

validateCredentials() accepts an instance of Credentials, which contains the user identifier and password extracted from the application request.

AuthHandler will call whichever method is appropriate depending on what data to authenticate is included in the application request.

If authentication is successful, the called method must return a populated instance of Token representing either the existing validated token or a new token corresponding to the existing user.

If the specified token or credentials are invalid, the called method must throw an instance of InvalidException.

If some other error condition occurs such that authentication cannot be completed successfully, the called method must throw an instance of AuthException.

The injector can be configured to use your adapter implementation like so:

use Equip\Auth\AdapterInterface;
use My\Auth\Adapter;

$injector->alias(
    AdapterInterface::class,
    Adapter::class
);

JSON Web Tokens

If you are using JWT, you of course have the option of using a related library directly in your adapter.

Another option is to use bundled library adapters. There are two related interfaces, Jwt\GeneratorInterface and Jwt\ParserInterface, which handle generating and parsing JWT tokens respectively. You can code your authentication adapter against these library adapter interfaces and then easily swap out implementations.

use Equip\Auth\AdapterInterface as Adapter;
use Equip\Auth\Jwt\GeneratorInterface as Generator;
use Equip\Auth\Jwt\ParserInterface as Parser;

class MyAdapter implements Adapter
{
    protected $generator;
    protected $parser;

    public function __construct(Generator $generator, Parser $parser)
    {
        $this->generator = $generator;
        $this->parser = $parser;
    }

    public function validateToken($token)
    {
        $parsed = $this->parser->parseToken((string) $token);

        // $parsed is an instance of \Equip\Auth\Token. You can call its
        // getMetadata() method here to get all metadata associated with the
        // token, such as a unique identifier for the user, in order to
        // validate the token.

        return $parsed;
    }

    public function validateCredentials(Credentials $credentials)
    {
        // Validate $credentials here, then assign to $claims an array
        // containing the JWT claims to associate with the generated token.

        // You can store metadata associated with the token such as a unique identifier
        // for the user. 
        $metadata = [];

        return new \Equip\Auth\Token($this->generator->getToken($claims), $metadata);
    }
}

See the JWT RFC for a list of registered claims.

Lcobucci

To use the lcobucci/jwt library:

composer require "lcobucci/jwt:^3"
$injector->define(
    'Equip\\Auth\\Jwt\\Configuration',
    [
        ':publicKey' => '...',
        ':ttl' => 3600, // in seconds, e.g. 1 hour
        ':algorithm' => 'HS256',
    ]
);
$injector->alias(
    'Equip\\Auth\\Jwt\\GeneratorInterface',
    'Equip\\Auth\\Jwt\\LcobucciGenerator'
);
$injector->alias(
    'Equip\\Auth\\Jwt\\ParserInterface',
    'Equip\\Auth\\Jwt\\LcobucciParser'
);
$injector->alias(
    'Lcobucci\\JWT\\Signer',
    'Lcobucci\\JWT\\Signer\\Hmac\\Sha256'
);

Firebase

To use the firebase/php-jwt library:

composer require "firebase/php-jwt:^3"
$injector->define(
    'Equip\\Auth\\Jwt\\Configuration',
    [
        ':publicKey' => '...',
        ':ttl' => 3600, // in seconds, e.g. 1 hour
        ':algorithm' => 'HS256',
    ]
);
$injector->alias(
    'Equip\\Auth\\Jwt\\GeneratorInterface',
    'Equip\\Auth\\Jwt\\FirebaseGenerator'
);
$injector->alias(
    'Equip\\Auth\\Jwt\\ParserInterface',
    'Equip\\Auth\\Jwt\\FirebaseParser'
);