Overview
Phalcon\Encryption\Security\JWT contains components to issue, parse, and validate JSON Web Tokens as described
in RFC 7519.
These components are:
- Builder (Phalcon\Encryption\Security\JWT\Builder)
- Parser (Phalcon\Encryption\Security\JWT\Token\Parser)
- Validator (Phalcon\Encryption\Security\JWT\Validator)
An example of using the component is:
<?php
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;
// Defaults to 'sha512'
$signer = new Hmac();
// Builder object
$builder = new Builder($signer);
$now = new DateTimeImmutable();
$issued = $now->getTimestamp();
$notBefore = $now->modify('-1 minute')->getTimestamp();
$expires = $now->modify('+1 day')->getTimestamp();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
// Setup
$builder
->setAudience('https://target.phalcon.io') // aud
->setContentType('application/json') // cty - header
->setExpirationTime($expires) // exp
->setId('abcd123456789') // JTI id
->setIssuedAt($issued) // iat
->setIssuer('https://phalcon.io') // iss
->setNotBefore($notBefore) // nbf
->setSubject('my subject for this claim') // sub
->setPassphrase($passphrase) // password
;
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $builder->getToken();
echo $tokenObject->getToken();
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg// $tokenReceived is what we received
$tokenReceived = getMyTokenFromTheApplication();
$audience = 'https://target.phalcon.io';
$now = new DateTimeImmutable();
$issued = $now->getTimestamp();
$notBefore = $now->modify('-1 minute')->getTimestamp();
$expires = $now->getTimestamp();
$id = 'abcd123456789';
$issuer = 'https://phalcon.io';
// Defaults to 'sha512'
$signer = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
// Parse the token
$parser = new Parser();
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $parser->parse($tokenReceived);
// Phalcon\Encryption\Security\JWT\Validator
$validator = new Validator($tokenObject, 100); // Allow for a time shift of 100
# Run the validators
$validator
->validateAudience($audience)
->validateExpiration($expires)
->validateId($id)
->validateIssuedAt($issued)
->validateIssuer($issuer)
->validateNotBefore($notBefore)
->validateSignature($signer, $passphrase)
;
# Errors printed out (if any)
var_dump($validator->getErrors())The above example gives a general view on how the component can be used to generate, parse, and validate JSON Web Tokens.
Objects
There are several utility components that live in the Phalcon\Encryption\Security\JWT\Token namespace, that help with
the issuing, parsing, and validating of JWT tokens
Enum
Phalcon\Encryption\Security\JWT\Token\Enum is a class that contains several constants. These constants are the strings defined in RFC 7915. You can use them if you wish or instead use their string equivalents.
<?php
class Enum
{
/**
* Headers
*/
const TYPE = "typ";
const ALGO = "alg";
const CONTENT_TYPE = "cty";
/**
* Claims
*/
const AUDIENCE = "aud";
const EXPIRATION_TIME = "exp";
const ID = "jti";
const ISSUED_AT = "iat";
const ISSUER = "iss";
const NOT_BEFORE = "nbf";
const SUBJECT = "sub";
}Item
Phalcon\Encryption\Security\JWT\Token\Item is used internally to store a payload as well as its encoded state. Such payload can be the claims data or the headers’ data. By using this component, we can easily extract the necessary information for each Token.
Signature
Phalcon\Encryption\Security\JWT\Token\Signature is similar to the Phalcon\Encryption\Security\JWT\Token\Item, but it only holds the signature hash as well as its encoded value.
Token
Phalcon\Encryption\Security\JWT\Token\Token is the component responsible for storing and calculating the JWT token. It accepts the headers, claims ( as Phalcon\Encryption\Security\JWT\Token\Item objects), and signature objects in its constructor and exposes:
public function getClaims(): ItemReturn the claims collection
public function getHeaders(): ItemReturn the headers collection
public function getPayload(): stringReturn the payload. For a token abcd.efgh.ijkl, it will return abcd.efgh
public function getSignature(): SignatureReturn the signature
public function getToken(): stringReturn the token as a string. For a token abcd.efgh.ijkl it will return abcd.efgh.ijkl.
public function validate(Validator $validator): arrayRun all validators against the token data. Return the errors array from the validator
public function verify(SignerInterface $signer, string $key): boolVerify the signature of the token
Signer
Supported signers
In order to create a JWT token, we need to supply a Signing algorithm. By default, the builder uses “ none“ (Phalcon\Encryption\Security\JWT\Signer\None). You can however use the HMAC signer (Phalcon\Encryption\Security\JWT\Signer\Hmac). Also, for further customization, you can utilize the supplied Phalcon\Encryption\Security\JWT\Signer\SignerInterface interface.
<?php
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
$signer = new Hmac();None
This signer is provided mostly for development purposes. You should always sign your JWT tokens.
HMAC
The HMAC signer supports the sha512, sha384, and sha256 algorithms. If none is supplied, the sha512 is
automatically selected. If you supply a different algorithm,
a Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException
will be raised. The algorithm is set in the constructor.
<?php
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
$signer = new Hmac();
$signer = new Hmac('sha512');
$signer = new Hmac('sha384');
$signer = new Hmac('sha256');
$signer = new Hmac('sha111'); // exceptionThe component utilizes the hash_equals and hash_hmac PHP methods internally to verify and sign the payload. It exposes the following methods:
public function getAlgHeader(): stringReturns a string identifying the algorithm. For the HMAC algorithms, it will return:
| Algorithm | getAlgHeader |
|---|---|
sha512 |
HS512 |
sha384 |
HS384 |
sha256 |
HS256 |
public function sign(string $payload, string $passphrase): stringReturns the hash of the payload using the passphrase
public function verify(string $source, string $payload, string $passphrase): boolVerifies that the hashed source string is the same as the hash of the payload with the passphrase.
Issuing Tokens
A Builder component (Phalcon\Encryption\Security\JWT\Builder) is available, utilizing chained
methods, and ready to be used to create JWT tokens. All you have to do is instantiate the Builder object, configure your
token, and call getToken(). This will return a Phalcon\Encryption\Security\JWT\Token\Token
object which contains all the necessary information for your token. When instantiating the builder component, you have
to supply the signer class. In the example below we use
the Phalcon\Encryption\Security\JWT\Signer\Hmac signer.
All setters in this component are chainable.
<?php
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
// Defaults to 'sha512'
$signer = new Hmac();
// Builder object
$builder = new Builder($signer);Methods
public function __construct(SignerInterface $signer): BuilderConstructor
public function init(): BuilderInitializes the object - useful when you want to reuse the same builder
public function addClaim(string $name, mixed $value): BuilderAdds a custom claim in the claims collection
public function getAudience(): arrayReturns the aud contents. If aud is not set, this method returns an empty array.
public function getClaims(): arrayReturns the claims as an array
public function getContentType(): ?stringReturns the content type (cty - headers)
public function getExpirationTime(): ?intReturns the exp contents
public function getHeaders(): arrayReturns the headers as an array
public function getId(): ?stringReturns the jti contents (ID of this JWT)
public function getIssuedAt(): ?intReturns the iat contents
public function getIssuer(): ?stringReturns the iss contents
public function getNotBefore(): ?intReturns the nbf contents
public function getSubject(): ?stringReturns the sub contents
public function getToken(): TokenReturns the token
public function getPassphrase(): stringReturns the supplied passphrase
public function setAudience(array|string $audience): BuilderSets the audience (aud). If the parameter passed is not an array or a string,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function setContentType(string $contentType): BuilderSets the content type (cty - headers)
public function setExpirationTime(int $timestamp): BuilderSets the audience (exp). If the $timestamp is less than the current time,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function setId(string $id): BuilderSets the id (jti).
public function setIssuedAt(int $timestamp): BuilderSets the issued at time (iat).
public function setIssuer(string $issuer): BuilderSets the issuer (iss).
public function setNotBefore(int $timestamp): BuilderSets the not before time (nbf). If the $timestamp is greater than the current time,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function setSubject(string $subject): BuilderSets the subject (sub).
public function setPassphrase(string $passphrase): BuilderSets the passphrase. A weak passphrase raises a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException.
The passphrase must:
- be at least 16 characters long
- contain at least one uppercase letter
- contain at least one lowercase letter
- contain at least one digit
- contain at least one special character
private function setClaim(string $name, $value): BuilderSets a claim value in the internal collection.
Example
<?php
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;
// 'sha512'
$signer = new Hmac();
$builder = new Builder($signer);
$now = new DateTimeImmutable();
$issued = $now->getTimestamp();
$notBefore = $now->modify('-1 minute')->getTimestamp();
$expires = $now->modify('+1 day')->getTimestamp();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
$builder
->setAudience('https://target.phalcon.io') // aud
->setContentType('application/json') // cty - header
->setExpirationTime($expires) // exp
->setId('abcd123456789') // JTI id
->setIssuedAt($issued) // iat
->setIssuer('https://phalcon.io') // iss
->setNotBefore($notBefore) // nbf
->setSubject('my subject for this claim') // sub
->setPassphrase($passphrase) // password
;
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $builder->getToken();
echo $tokenObject->getToken();
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbgValidating Tokens
In order to validate a token you will need to create a new Phalcon\Encryption\Security\JWT\Validator object. The object can be constructed using a Phalcon\Encryption\Security\JWT\Token\Token object and an offset in time to handle time/clock shifts of the sending and receiving computers.
In order to parse the JWT received and convert it to a Phalcon\Encryption\Security\JWT\Token\Token object, you will need to use a Phalcon\Encryption\Security\JWT\Token\Parser object and parse it.
Validator
$parser = new Parser();
$tokenObject = $parser->parse($tokenReceived);
$validator = new Validator($tokenObject, 100); // allow for a time shift of 100You can use the Phalcon\Encryption\Security\JWT\Validator object to validate each claim by
calling the validate* methods with the necessary parameters (taken from
the Phalcon\Encryption\Security\JWT\Token\Token). The internal errors array in
the Phalcon\Encryption\Security\JWT\Validator will be populated accordingly, returning the
results with the getErrors() method.
Methods
public function __construct(Token $token, int $timeShift = 0)Constructor
public function get(string $claim): mixed | nullReturns a claim’s value - null if the claim does not exist
public function set(string $claim, mixed $value): ValidatorSets a claim and its value
public function setToken(Token $token): ValidatorSets the token object.
public function validateAudience(array|string $audience): ValidatorValidates the audience. If it is not included in the token’s aud,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateClaim(string $name, mixed $value): ValidatorValidates a custom claim by name against an expected value.
In a validator example chain, include one custom claim check, e.g.:
$validator
->validateAudience($audience)
->validateIssuer($issuer)
->validateClaim("tenantId", "acme")
;public function validateExpiration(int $timestamp): ValidatorValidates the expiration time. If the exp value stored in the token is less than now,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateId(string $id): ValidatorValidates the id. If it is not the same as the jti value stored in the token,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateIssuedAt(int $timestamp): ValidatorValidates the issued at time. If the iat value stored in the token is greater than now,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateIssuer(string $issuer): ValidatorValidates the issuer. If it is not the same as the iss value stored in the token,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateNotBefore(int $timestamp): ValidatorValidates the not before time. If the nbf value stored in the token is greater than now,
a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be
thrown.
public function validateSignature(SignerInterface $signer, string $passphrase): ValidatorValidates the signature of the token. If the signature is not valid, a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be thrown.
Example
<?php
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg
$tokenReceived = getMyTokenFromTheApplication();
$audience = 'https://target.phalcon.io';
$now = new DateTimeImmutable();
$issued = $now->getTimestamp();
$notBefore = $now->modify('-1 minute')->getTimestamp();
$expires = $now->getTimestamp();
$id = 'abcd123456789';
$issuer = 'https://phalcon.io';
// 'sha512'
$signer = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
$parser = new Parser();
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $parser->parse($tokenReceived);
// Phalcon\Encryption\Security\JWT\Validator
$validator = new Validator($tokenObject, 100); // allow for a time shift of 100
$validator
->validateAudience($audience)
->validateExpiration($expires)
->validateId($id)
->validateIssuedAt($issued)
->validateIssuer($issuer)
->validateNotBefore($notBefore)
->validateSignature($signer, $passphrase)
;
var_dump($validator->getErrors());Token
As an alternative, you can verify() and validate() your token using the relevant methods in
the Phalcon\Encryption\Security\JWT\Token\Token object.
Methods
public function validate(Validator $validator): arrayValidate the token claims. The validators that are executed are:
validateAudience()validateExpiration()validateId()validateIssuedAt()validateIssuer()validateNotBefore()
You can extend the Phalcon\Encryption\Security\JWT\Validator and Phalcon\Encryption\Security\JWT\Token\Token objects to include more validators and execute them (as seen below).
public function verify(SignerInterface $signer, string $key): boolVerify the signature of the token
Example
<?php
use Phalcon\Encryption\Security\JWT\Enum;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Token\Parser;
use Phalcon\Encryption\Security\JWT\Validator;
// eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzUxMiIsImN0eSI6ImFwcGxpY2F0aW9uXC9qc29uIn0.
// eyJhdWQiOlsiaHR0cHM6XC9cL3RhcmdldC5waGFsY29uLmlvIl0sImV4cCI6MTYxNDE4NTkxN
// ywianRpIjoiYWJjZDEyMzQ1Njc4OSIsImlhdCI6MTYxNDA5OTUxNywiaXNzIjoiaHR0cHM6XC
// 9cL3BoYWxjb24uaW8iLCJuYmYiOjE2MTQwOTk0NTcsInN1YiI6Im15IHN1YmplY3QgZm9yIHR
// oaXMgY2xhaW0ifQ.
// LdYevRZaQDZ2lul4CCQ5DymeP2ubcapTtgeezOZGIq7Meu7rFF1pv32b-AMWOxCS63CQz_jpm
// BPlPyOeEAkMbg
$tokenReceived = getMyTokenFromTheApplication();
$subject = 'Mary had a little lamb';
$audience = 'https://target.phalcon.io';
$now = new DateTimeImmutable();
$issued = $now->getTimestamp();
$notBefore = $now->modify('-1 minute')->getTimestamp();
$expires = $now->getTimestamp();
$id = 'abcd123456789';
$issuer = 'https://phalcon.io';
// 'sha512'
$signer = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
$parser = new Parser();
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $parser->parse($tokenReceived);
// Phalcon\Encryption\Security\JWT\Validator
$validator = new Validator($tokenObject, 100); // allow for a time shift of 100
$validator
->set(Enum::AUDIENCE, $audience)
->set(Enum::EXPIRATION_TIME, $expires)
->set(Enum::ISSUER, $issuer)
->set(Enum::ISSUED_AT, $issued)
->set(Enum::ID, $id)
->set(Enum::NOT_BEFORE, $notBefore)
->set(Enum::SUBJECT, $subject)
;
$tokenObject->verify($signer, $passphrase);
$errors = $tokenObject->validate($validator);
var_dump($errors);Exceptions
Any exceptions thrown in the Security component will be of the namespace Phalcon\Encryption\Security\JWT\*. You can
use this exception to selectively catch exceptions thrown only from this component. There are two exceptions raised.
First, if you supply the wrong algorithm string when instantiating
the Phalcon\Encryption\Security\JWT\Signer\Hmac component. This exception
is Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException.
The second exception is thrown when validating a JWT. This exception is Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException.
<?php
use Phalcon\Mvc\Controller;
use Phalcon\Encryption\Security\JWT\Builder;
use Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException;
use Phalcon\Encryption\Security\JWT\Signer\Hmac;
use Phalcon\Encryption\Security\JWT\Validator;
class IndexController extends Controller
{
public function index()
{
try {
$signer = new Hmac();
$builder = new Builder($signer);
$expires = strtotime('+1 day');
$issued = strtotime('now') + 100;
$notBefore = strtotime('-1 day');
$passphrase = '&vsJBETaizP3A3VX&TPMJUqi48fJEgN7';
return $builder
->setAudience('my-audience')
->setExpirationTime($expires)
->setIssuer('Phalcon JWT')
->setIssuedAt($issued)
->setId('PH-JWT')
->setNotBefore($notBefore)
->setSubject('Mary had a little lamb')
->setPassphrase($passphrase)
->getToken()
;
$validator = new Validator($token);
$validator->validateAudience("unknown");
} catch (Exception $ex) {
echo $ex->getMessage(); // Validation: audience not allowed
}
}
}Granular Exceptions
As of 5.13.1 the component raises granular subclasses so callers can catch a specific failure mode. Validation-related
classes extend ValidatorException; structural-input errors extend InvalidArgumentException; the HMAC algorithm
rejection extends UnsupportedAlgorithmException.
| Class | Parent | Thrown when |
|---|---|---|
Phalcon\Encryption\Security\JWT\Exceptions\EmptyPassphrase |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
A signing or validation call is made without a passphrase. |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidAudience |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
The token’s aud claim does not match the audience supplied to the validator. |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidAudienceType |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
The audience supplied to the validator is not a string or array of strings. |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidClaims |
InvalidArgumentException |
The decoded claims segment is not a JSON object. |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidExpirationTime |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
The token has expired (exp claim is in the past). |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidHeader |
InvalidArgumentException |
The decoded header segment is not a JSON object. |
Phalcon\Encryption\Security\JWT\Exceptions\InvalidNotBefore |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
The token is presented before its nbf claim. |
Phalcon\Encryption\Security\JWT\Exceptions\MalformedJwtString |
InvalidArgumentException |
The supplied JWT string does not have three dot-separated segments. |
Phalcon\Encryption\Security\JWT\Exceptions\MissingJwtTypHeader |
InvalidArgumentException |
The decoded header is missing the typ field. |
Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedHmacAlgorithm |
Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException |
The HMAC signer is constructed with an algorithm not in the supported list. |
Phalcon\Encryption\Security\JWT\Exceptions\WeakPassphrase |
Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException |
The supplied passphrase is below the minimum strength threshold. |