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#
encoderequiresnbfandexpclaims.decoderequires 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;