Seguridad - Tokens Web JSON (JWT)

Resumen
NOTA: Actualmente, sólo se soportan algoritmos simétricos
Phalcon\Encryption\Security\JWT
is a namespace that contains components that allow you to issue, parse and validate JSON Web Tokens as described in RFC 7915. Estos componentes son:
NOTE: For the examples below, we have split the output split into different lines for readability
Un ejemplo de uso del componente es:
<?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
// #01
$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';
// #02
$signer = new Hmac();
$passphrase = 'QcMpZ&b&mo3TPsPk668J6QH8JA$&U&m2';
// #03
$parser = new Parser();
// Phalcon\Encryption\Security\JWT\Token\Token
$tokenObject = $parser->parse($tokenReceived);
// Phalcon\Encryption\Security\JWT\Validator
$validator = new Validator($tokenObject, 100); // #04
# 05
$validator
->validateAudience($audience)
->validateExpiration($expires)
->validateId($id)
->validateIssuedAt($issued)
->validateIssuer($issuer)
->validateNotBefore($notBefore)
->validateSignature($signer, $passphrase)
;
# 06
var_dump($validator->getErrors())
LEGEND
-
$tokenReceived is what we received
-
Defaults to ‘sha512’
-
Parse the token
-
Allow for a time shift of 100
-
Run the validators
-
Errors printed out (if any)
El ejemplo anterior da una vista general de cómo se puede usar el componente para generar, analizar y validar Tokens Web JSON.
Objetos
There are several utility components that live in the Phalcon\Encryption\Security\JWT\Token
namespace, that help with the issuing, parsing and validating JWT tokens
Enum
Phalcon\Encryption\Security\JWT\Token\Enum is a class that contains several constants. Estas constantes son cadenas definidas en RFC 7915. Puede usarlas si desea o en su lugar usar sus cadenas equivalentes.
<?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. Al usar este componente, podemos extraer fácilmente la información necesaria de cada 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(): Item
Return the claims collection
public function getHeaders(): Item
Return the headers collection
public function getPayload(): string
Return the payload. For a token abcd.efgh.ijkl
, it will return abcd.efgh
public function getSignature(): Signature
Return the signature
public function getToken(): string
Return the token as a string. For a token abcd.efgh.ijkl
it will return abcd.efgh.ijkl
.
public function validate(Validator $validator): array
Run all validators against the token data. Return the errors array from the validator
public function verify(SignerInterface $signer, string $key): bool
Verify the signature of the token
Signer
Para crear un token JWT, necesitamos suministrar un algoritmo de Firma. 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
El firmante se proporciona principalmente para propósitos de desarrollo. Siempre debería firmar sus tokens JWT.
HMAC
El firmante HMAC soporta los algoritmos sha512
, sha384
, y sha256
. Si no se proporciona ninguno, se seleccionará automáticamente sha512
. If you supply a different algorithm, a Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException will be raised. El algoritmo se establece en el 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'); // exception
El componente usa internamente los métodos PHP [hmac_equals][hmac_equals] y [hash_hmac][hash_hmac] para verificar y firmar la carga útil. Expone los siguientes métodos:
public function getAlgHeader(): string
Devuelve una cadena que identifica el algoritmo. For the HMAC algorithms it will return:
Algoritmo |
getAlgHeader |
sha512 |
HS512 |
sha384 |
HS384 |
sha256 |
HS256 |
public function sign(string $payload, string $passphrase): string
Devuelve el hash de la carga útil usando la frase secreta
public function verify(string $source, string $payload, string $passphrase): bool
Verifica que el hash de la cadena original es el mismo que el hash de la carga útil con la frase secreta.
Emisión de Tokens
A Builder component (Phalcon\Encryption\Security\JWT\Builder) is available, utilizing chained methods, and ready to be used to create JWT tokens. Todo lo que tiene que hacer es instanciar el objeto Builder
, configurar su token y llamar getToken()
. This will return a Phalcon\Encryption\Security\Token\Token object which contains all the necessary information for your token. Al instanciar el componente constructor, debe proporcionar la clase de firmante. In the example below we use the Phalcon\Encryption\Security\JWT\Signer\Hmac signer.
Todos los setters de este componente son encadenables.
<?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);
Métodos
public function __construct(SignerInterface $signer): Builder
Constructor
public function init(): Builder
Inicializa el objeto - útil cuando quiere reutilizar el mismo constructor
public function addClaim(string $name, mixed $value): Builder
Adds a custom claim in the claims collection
public function getAudience(): array|string
Obtiene el contenido de aud
public function getClaims(): array
Obtiene los reclamos como vector
public function getContentType(): ?string
Devuelve el tipo de contenido (cty
- cabeceras)
public function getExpirationTime(): ?int
Devuelve el contenido de exp
public function getHeaders(): array
Devuelve las cabeceras como vector
public function getId(): ?string
Devuelve el contenido de jti
(ID de este JWT)
public function getIssuedAt(): ?int
Devuelve el contenido de iat
public function getIssuer(): ?string
Devuelve el contenido de iss
public function getNotBefore(): ?int
Devuelve el contenido de nbf
public function getSubject(): ?string
Devuelve el contenido de sub
public function getToken(): Token
Devuelve el token
public function getPassphrase(): string
Devuelve la frase secreta proporcionada
public function setAudience(array|string $audience): Builder
Establece la audiencia (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): Builder
Establece el tipo de contenido (cty
- cabeceras)
public function setExpirationTime(int $timestamp): Builder
Establece la audiencia (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): Builder
Establece el id (jti
).
public function setIssuedAt(int $timestamp): Builder
Establece el emitido a la hora (iat
).
public function setIssuer(string $issuer): Builder
Establece el emisor (iss
).
public function setNotBefore(int $timestamp): Builder
Sets el tiempo no anterior (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): Builder
Establece el asunto (sub
).
public function setPassphrase(string $passphrase): Builder
Establece la frase secreta. If the $passphrase
is weak, a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be thrown.
private function setClaim(string $name, $value): Builder
Establece un valor de reclamo en la colección interna.
Ejemplo
<?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
// BPlPyOeEAkMbg
Validación de 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 100
You 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\Token\Token). The internal errors
array in the Phalcon\Encryption\Security\JWT\Validator will be populated accordingly, returning the results with the getErrors()
method.
Métodos
public function __construct(Token $token, int $timeShift = 0)
Constructor
public function get(string $claim): mixed | null
Returns a claim’s value - null
if the claim does not exist
public function set(string $claim, mixed $value): Validator
Sets a claim and its value
public function setToken(Token $token): Validator
Establece el objeto token.
public function validateAudience(array|string $audience): Validator
Valida la audiencia. If it is not included in the token’s aud
, a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be thrown.
public function validateExpiration(int $timestamp): Validator
Valida el tiempo de expiración. If the exp
value stored in the token is greater than now, a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be thrown.
public function validateId(string $id): Validator
Valida el 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): Validator
Valida el emitido a la hora. 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): Validator
Valida el emisor. 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): Validator
Valida el tiempo no anterior. 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): Validator
Valida al firma del token. If the signature is not valid, a Phalcon\Encryption\Security\JWT\Exceptions\ValidatorException will be thrown.
Ejemplo
<?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\Token\Token object.
Métodos
public function validate(Validator $validator): array
Validate 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\Token\Token objects to include more validators and execute them (as seen below).
public function verify(SignerInterface $signer, string $key): bool
Verify the signature of the token
Ejemplo
<?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, $expiry)
->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);
Excepciones
Any exceptions thrown in the Security component will be of the namespace Phalcon\Encryption\Security\JWT\*
. Puede usar esta excepción para capturar selectivamente sólo las excepciones lanzadas desde este componente. Hay dos excepciones lanzadas. First if you supply the wrong algoritm string when instantiating the Phalcon\Encryption\Security\JWT\Signer\Hmac component. This exception is Phalcon\Encryption\Security\JWT\Exceptions\UnsupportedAlgorithmException.
La segunda excepción se lanza cuando se valida un 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);
$expiry = strtotime('+1 day');
$issued = strtotime('now') + 100;
$notBefore = strtotime('-1 day');
$passphrase = '&vsJBETaizP3A3VX&TPMJUqi48fJEgN7';
return $builder
->setAudience('my-audience')
->setExpirationTime($expiry)
->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
}
}
}