Token Domain#

Namespace: Infocyph\\Epicrypt\\Token

Scope#

  • JWT (symmetric and asymmetric)

  • signed payload tokens

  • opaque tokens

  • claim validation and key resolution

  • key-ring verification helpers for signed payload and JWT rotation

  • JWKS/JWK export, import and kid-based verification flows

  • structured verification result objects for safer application decisions

Symmetric JWT#

use Infocyph\Epicrypt\Security\Policy\SecurityProfile;
use Infocyph\Epicrypt\Token\Jwt\SymmetricJwt;
use Infocyph\Epicrypt\Token\Jwt\Validation\RegisteredClaims;

$now = time();
$claims = [
    'iss' => 'issuer-service',
    'aud' => 'audience-service',
    'sub' => 'subject-service',
    'jti' => 'token-1',
    'nbf' => $now,
    'exp' => $now + 600,
    'scope' => 'admin',
];

$token = SymmetricJwt::forProfile(SecurityProfile::MODERN)->encode($claims, 'super-secret-key');

$jwt = SymmetricJwt::forProfile(
    SecurityProfile::MODERN,
    new RegisteredClaims('issuer-service', 'audience-service', 'subject-service', 'token-1'),
);

$decoded = $jwt->decode($token, 'super-secret-key');
$isValid = $jwt->verify($token, 'super-secret-key');

Important#

  • encode requires nbf and exp claims.

  • decode requires expected claims (RegisteredClaims) in constructor.

JWT Key Rings#

use Infocyph\Epicrypt\Security\KeyRing;
use Infocyph\Epicrypt\Security\Policy\SecurityProfile;
use Infocyph\Epicrypt\Token\Jwt\SymmetricJwt;
use Infocyph\Epicrypt\Token\Jwt\Validation\RegisteredClaims;

$jwt = SymmetricJwt::forProfile(
    SecurityProfile::MODERN,
    new RegisteredClaims('issuer-service', 'audience-service', 'subject-service', 'token-1'),
);

$ring = new KeyRing(['previous' => 'previous-secret', 'active' => 'active-secret'], 'active');
$claims = $jwt->decodeWithAnyKey($token, $ring);
$isValid = $jwt->verifyWithAnyKey($token, $ring);
$result = $jwt->verifyWithAnyKeyResult($token, $ring);

Asymmetric JWT#

use Infocyph\Epicrypt\Security\Policy\SecurityProfile;
use Infocyph\Epicrypt\Token\Jwt\AsymmetricJwt;
use Infocyph\Epicrypt\Token\Jwt\Validation\RegisteredClaims;

use Infocyph\Epicrypt\Certificate\KeyPairGenerator;

$keys = KeyPairGenerator::openSsl()->generate();
$privateKey = $keys['private'];
$publicKey = $keys['public'];

$now = time();
$claims = [
    'iss' => 'issuer-service',
    'aud' => 'audience-service',
    'sub' => 'subject-service',
    'jti' => 'token-1',
    'nbf' => $now,
    'exp' => $now + 600,
];

$token = AsymmetricJwt::forProfile(SecurityProfile::MODERN)->encode($claims, $privateKey);
$jwt = AsymmetricJwt::forProfile(
    SecurityProfile::MODERN,
    new RegisteredClaims('issuer-service', 'audience-service', 'subject-service', 'token-1'),
);
$isValid = $jwt->verify($token, $publicKey);

JWT Result APIs#

Use result APIs when you need structured verification state instead of only boolean pass/fail.

use Infocyph\Epicrypt\Token\Jwt\Validation\ExpectedJwtClaims;
use Infocyph\Epicrypt\Token\Jwt\Validation\RequiredJwtClaims;
use Infocyph\Epicrypt\Token\Jwt\Validation\JwtValidationOptions;

$jwt = SymmetricJwt::forProfile(
    SecurityProfile::MODERN,
    new ExpectedJwtClaims(
        issuer: 'issuer-service',
        audience: 'audience-service',
        subject: 'subject-service',
        required: new RequiredJwtClaims(issuer: true, audience: true, subject: true),
    ),
    new JwtValidationOptions(strictTyp: true, leewaySeconds: 15),
);

$result = $jwt->decodeResult($token, 'super-secret-key');
if ($result->verified) {
    $claims = $result->claims;
    $headers = $result->headers;
}

// Metadata fields:
// $result->matchedKeyId
// $result->usedFallbackKey
// $result->expired
// $result->notBeforeViolation
// $result->algorithm

Signed Payload Token#

use Infocyph\Epicrypt\Token\Payload\SignedPayload;

$payload = new SignedPayload('reset_password');

$token = $payload->encode(
    ['sub' => 'user-1', 'purpose' => 'reset'],
    'payload-secret',
    ['exp' => time() + 600],
);

$claims = $payload->decode($token, 'payload-secret');
$isValid = $payload->verify($token, 'payload-secret');

Signed Payload Key Rings#

use Infocyph\Epicrypt\Security\KeyRing;
use Infocyph\Epicrypt\Token\Payload\SignedPayload;

$payload = new SignedPayload('reset_password');
$ring = new KeyRing(['previous' => 'previous-secret', 'active' => 'active-secret'], 'active');
$claims = $payload->decodeWithAnyKey($token, $ring);
$isValid = $payload->verifyWithAnyKey($token, $ring);
$result = $payload->verifyWithAnyKeyResult($token, $ring);
$detailed = $payload->verifyWithAnyKeyDetailedResult($token, $ring);

Signed Payload Result APIs#

$result = $payload->verifyResult($token, 'payload-secret');
if ($result->verified) {
    $claims = $result->claims;
}

// Metadata fields:
// $result->matchedKeyId
// $result->usedFallbackKey
// $result->expired

Opaque Token#

use Infocyph\Epicrypt\Token\Opaque\OpaqueToken;

$opaque = new OpaqueToken();
$token = $opaque->issue(48);
$digest = $opaque->hash($token);
$isValid = $opaque->verify($token, $digest);

JWKS/JWK Interoperability#

Use this for asymmetric JWT interop where verifier keys are distributed in JWKS form.

use Infocyph\Epicrypt\Security\KeyRing;
use Infocyph\Epicrypt\Token\Jwt\AsymmetricJwt;
use Infocyph\Epicrypt\Token\Jwt\Jwks;

$jwksHelper = new Jwks();
$publicRing = new KeyRing(['k1' => $publicKey1, 'k2' => $publicKey2], 'k2');
$jwks = $jwksHelper->exportFromKeyRing($publicRing);

// Resolve by kid to PEM:
$pem = $jwksHelper->resolvePublicKeyByKid($jwks, 'k2');

// Verify directly from JWKS:
$jwt = AsymmetricJwt::forProfile(SecurityProfile::MODERN, $expectedClaims);
$result = $jwt->verifyFromJwksResult($token, $jwks);
$isValid = $result->verified;