Skip to content

Signed tokens (JWS) examples

Sign a token with HMAC (HS256)

The simplest approach: a shared secret key is used to both sign and verify the token.

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Signature\Algorithm\HS256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

// Create a symmetric key (shared secret)
$jwk = new JWK([
    'kty' => 'oct',
    'k' => base64_encode(random_bytes(32)), // 256-bit key
]);

$algorithmManager = new AlgorithmManager([new HS256()]);
$jwsBuilder = new JWSBuilder($algorithmManager);

$payload = json_encode([
    'iss' => 'https://my-app.example.com',
    'aud' => 'https://api.example.com',
    'sub' => 'user-42',
    'iat' => time(),
    'exp' => time() + 3600,
]);

$jws = $jwsBuilder
    ->withPayload($payload)
    ->addSignature($jwk, ['alg' => 'HS256'])
    ->build();

$token = (new CompactSerializer())->serialize($jws);
// eyJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwczovL215LWFwcC...

Verify the token

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Signature\Algorithm\HS256;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

$jwk = /* the same shared key used for signing */;

$algorithmManager = new AlgorithmManager([new HS256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);

$jws = (new CompactSerializer())->unserialize($token);

$isValid = $jwsVerifier->verifyWithKey($jws, $jwk, 0);
// $isValid is true if the signature is valid

Sign a token with RSA (RS256)

RSA keys allow the signer and the verifier to use different keys: a private key to sign, a public key to verify.

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

// Generate an RSA key pair (2048-bit minimum recommended)
$jwkFactory = new JWKFactory();
$privateKey = $jwkFactory->rsa(2048, ['alg' => 'RS256', 'use' => 'sig']);

$algorithmManager = new AlgorithmManager([new RS256()]);
$jwsBuilder = new JWSBuilder($algorithmManager);

$payload = json_encode([
    'iss' => 'https://auth.example.com',
    'aud' => 'https://api.example.com',
    'sub' => 'user-42',
    'iat' => time(),
    'exp' => time() + 3600,
]);

$jws = $jwsBuilder
    ->withPayload($payload)
    ->addSignature($privateKey, ['alg' => 'RS256'])
    ->build();

$token = (new CompactSerializer())->serialize($jws);

Verify with the public key

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\Algorithm\RS256;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

// Extract the public key from the private key
$publicKey = $privateKey->toPublic();

$algorithmManager = new AlgorithmManager([new RS256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);

$jws = (new CompactSerializer())->unserialize($token);

$isValid = $jwsVerifier->verifyWithKey($jws, $publicKey, 0);

Sign a token with elliptic curve (ES256)

ECDSA keys are smaller and faster than RSA for the same security level.

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

// Generate an EC key on the P-256 curve
$jwkFactory = new JWKFactory();
$privateKey = $jwkFactory->ec('P-256', ['alg' => 'ES256', 'use' => 'sig']);

$algorithmManager = new AlgorithmManager([new ES256()]);
$jwsBuilder = new JWSBuilder($algorithmManager);

$payload = json_encode([
    'iss' => 'https://auth.example.com',
    'aud' => 'https://api.example.com',
    'sub' => 'user-42',
    'iat' => time(),
    'exp' => time() + 3600,
]);

$jws = $jwsBuilder
    ->withPayload($payload)
    ->addSignature($privateKey, ['alg' => 'ES256'])
    ->build();

$token = (new CompactSerializer())->serialize($jws);

Verify with the public key

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\Algorithm\ES256;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

$publicKey = $privateKey->toPublic();

$algorithmManager = new AlgorithmManager([new ES256()]);
$jwsVerifier = new JWSVerifier($algorithmManager);

$jws = (new CompactSerializer())->unserialize($token);

$isValid = $jwsVerifier->verifyWithKey($jws, $publicKey, 0);

Sign a token with ed25519

Ed25519 provides high performance and strong security. Since 4.3, the fully-specified Ed25519 algorithm of RFC 9864 replaces the deprecated EdDSA; the key is the same, only the alg value differs. Requires the sodium extension, or PHP 8.4 (OpenSSL).

<?php

use Jose\Component\Core\AlgorithmManager;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\Algorithm\Ed25519;
use Jose\Component\Signature\JWSBuilder;
use Jose\Component\Signature\Serializer\CompactSerializer;

require_once 'vendor/autoload.php';

// Generate an Ed25519 key pair
$jwkFactory = new JWKFactory();
$privateKey = $jwkFactory->okp('Ed25519', ['alg' => 'Ed25519', 'use' => 'sig']);

$algorithmManager = new AlgorithmManager([new Ed25519()]);
$jwsBuilder = new JWSBuilder($algorithmManager);

$payload = json_encode([
    'iss' => 'https://auth.example.com',
    'sub' => 'user-42',
    'iat' => time(),
    'exp' => time() + 3600,
]);

$jws = $jwsBuilder
    ->withPayload($payload)
    ->addSignature($privateKey, ['alg' => 'Ed25519'])
    ->build();

$token = (new CompactSerializer())->serialize($jws);

// Verification
$publicKey = $privateKey->toPublic();
$jwsVerifier = new \Jose\Component\Signature\JWSVerifier($algorithmManager);
$jws = (new CompactSerializer())->unserialize($token);
$isValid = $jwsVerifier->verifyWithKey($jws, $publicKey, 0);

The JWSLoader combines deserialization, header checking, and signature verification in a single step. This is the recommended approach for loading tokens.

<?php

use Jose\Component\Checker\AlgorithmChecker;
use Jose\Component\Checker\HeaderCheckerManager;
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Core\JWK;
use Jose\Component\Signature\Algorithm\HS256;
use Jose\Component\Signature\JWSLoader;
use Jose\Component\Signature\JWSTokenSupport;
use Jose\Component\Signature\JWSVerifier;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\Signature\Serializer\JWSSerializerManager;

require_once 'vendor/autoload.php';

$jwk = /* your verification key */;

$algorithmManager = new AlgorithmManager([new HS256()]);

$jwsLoader = new JWSLoader(
    new JWSSerializerManager([new CompactSerializer()]),
    new JWSVerifier($algorithmManager),
    new HeaderCheckerManager(
        [new AlgorithmChecker(['HS256'])],  // Only allow HS256
        [new JWSTokenSupport()]
    )
);

$jws = $jwsLoader->loadAndVerify($token, $jwk)->getJws();

$payload = json_decode($jws->getPayload(), true);

Info

After verifying the signature, you should also validate the claims (exp, iss, aud, etc.). See the Claim and Header Validation page.