web-token/jwt-signature-algorithm-eddsa
EdDSA (Ed25519) signature algorithm support for the web-token/jwt-framework. Adds Ed25519 signing and verification to PHP JWT applications, letting you issue and validate compact JWS tokens with modern elliptic-curve cryptography.
What this package is. A single-purpose leaf of the web-token/jwt-* family. It ships one class — Eddsa (JWS alg: EdDSA, Ed25519 curve, RFC 8037) — that plugs into the framework's AlgorithmManager. There is no service provider, no config, no facade. In Laravel you wire it yourself.
Install.
composer require web-token/jwt-signature-algorithm-eddsa
# You almost certainly also need:
composer require web-token/jwt-signature web-token/jwt-core
# or just take the meta-package, which pulls this in transitively:
composer require web-token/jwt-library
Check composer show web-token/jwt-signature-algorithm-eddsa first — if you already depend on jwt-library or jwt-framework, it is probably already there.
Prerequisites. PHP 7.2+ with ext-sodium (php -m | grep sodium). Sodium is bundled with PHP 7.2+, so this is normally a no-op; only very old/stripped images need paragonie/sodium_compat.
Where to look first. vendor/web-token/jwt-signature-algorithm-eddsa/src/Eddsa.php — it is ~40 lines. Read it once and you understand the whole package: sign() reads d off the JWK and calls sodium_crypto_sign_detached, verify() reads x and calls sodium_crypto_sign_verify_detached.
First use case — sign and verify a detached JWS.
use Jose\Component\Core\JWK;
use Jose\Component\Signature\Algorithm\Eddsa;
// Generate once, store privately (base64url, no padding — same variant JWK uses)
$kp = sodium_crypto_sign_keypair();
$priv = sodium_crypto_sign_secretkey($kp);
$pub = sodium_crypto_sign_publickey($kp);
$b64 = fn (string $b) => sodium_bin2base64($b, SODIUM_BASE64_VARIANT_URLSAFE_NO_PADDING);
$privateJwk = new JWK(['kty' => 'OKP', 'crv' => 'Ed25519', 'd' => $b64($priv), 'x' => $b64($pub)]);
$publicJwk = new JWK(['kty' => 'OKP', 'crv' => 'Ed25519', 'x' => $b64($pub)]);
$alg = new Eddsa();
$sig = $alg->sign('header.payload', $privateJwk);
$ok = $alg->verify($publicJwk, 'header.payload', $sig); // true
If you are on a v2 install the namespace is WebToken\JWT\Signer\Algorithm\Eddsa and you construct it as a Signer with WebToken\JWT\Signer\Key\InMemory::base64Encoded(...). Grep the vendor directory before copying snippets from the internet.
1. Bind the algorithm once in a service provider. The classes are stateless and cheap, but you want the AlgorithmManager to be the single source of truth so nothing constructs a mismatched set of algorithms.
// app/Providers/AppServiceProvider.php
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\Algorithm\Eddsa;
$this->app->singleton(AlgorithmManager::class, fn () => new AlgorithmManager([
new Eddsa(),
]));
2. Signing a JWT (v3 API, JWSBuilder).
use Jose\Component\Signature\JWSBuilder;
$jwsBuilder = new JWSBuilder(app(AlgorithmManager::class));
$jws = $jwsBuilder
->create()
->withPayload(json_encode($claims))
->addSignature($privateJwk, ['alg' => 'EdDSA', 'typ' => 'JWT', 'kid' => $kid])
->build();
$token = $jws->getSignature(0)->getSignature(); // detatched sig
// or serialize the full compact JWS via your serializer of choice
Do not sign the raw JSON — the compact form signs base64url(header).base64url(payload). Using the builder is what saves you from getting this wrong by hand.
3. Verifying. Verification should be keyed by kid against a public key set (JWKS), not by a single hard-coded key, if you ever rotate:
use Jose\Component\Signature\JWSVerifier;
$verifier = new JWSVerifier(app(AlgorithmManager::class));
$jwk = $this->keyStore->find($jws->getSignature(0)->getProtectedHeader()['kid']);
$valid = $verifier->verifyWithKey($jws, $jwk, 0);
4. Layering with claim checks. This package only proves the signature. Pair it with web-token/jwt-checker (exp/iat/nbf/aud/iss) and your own iss/aud assertions, or do them with Laravel's validator. Never trust a JWS you have only signature-verified.
5. Key storage in Laravel. Keep the private JWK in a secrets manager or env() holding the base64url d; keep the public set in config/jwt.php (or serve it from a /.well-known/jwks.json route). A JWKS route built from ['kty','crv','x','kid'] is the standard pattern for a Laravel API issuing tokens to other services.
6. Interop. Tokens are standard RFC 8037 JWTs — lcobucci/jwt v5 and firebase/php-jwt both read them natively. Use this package when you are already inside web-token (you have jwt-encryption, jwt-checker, or the framework's other components); if you are building a single-issuer API from scratch, firebase/php-jwt is lighter and does EdDSA out of the box.
7. Testing. Assert on RFC 8037 Appendix A test vectors, not on your own output. EdDSA is deterministic, so a golden-file test of a signed token is actually meaningful (unlike ECDSA), but pinning test tokens to your key still rots if the key changes.
JWK values must use the URL-safe alphabet without =. A base64_encode()d key produces a valid-looking JWK whose signatures silently never verify. This is the #1 source of "it doesn't work" with this package.d vs x are not interchangeable. sign() requires d (and will throw if missing); verify() requires x. A "full" keypair JWK with both works for both, but never ship d to a verifier.alg is EdDSA, not Ed25519. The header value is the JWS algorithm name. The crv is Ed25519. Mixing them makes other libraries reject the token. Ed25519 as an algorithm name only appears in some non-standard tooling.SignatureAlgorithm wrapper around sodium_crypto_sign_*448_*.AlgorithmManager must include Eddsa. If you build a JWSVerifier with only HS256, verifyWithKey throws SignatureException ("algorithm not supported"), not a generic false. Catch Jose\Component\Signature\Exception\* and Jose\Component\Core\Exception\JWKException, and translate them to a 401 — never let the library exception's message reach the client, it often leaks header content.kid auto-population. If you rotate keys, you must set kid in addSignature() and implement lookup yourself; this package will happily verify against the wrong key and return false with no indication why.Jose\Component\... (v1, v3) vs WebToken\... (v2) — the web-token/* packages have flipped their namespace twice. The v2 "Signer/InMemory" API and the v3 "Algorithm/JWK" API are not compatible. Always check the installed major with composer show before copying examples.ext-sodium is missing, some installs pull paragonie/sodium_compat; verification then becomes pure-PHP. Fine in dev, a latency problem in a token-heavy API. Fail fast: if (!extension_loaded('sodium')) throw ... at boot in production.Jose\Component\Signature\Algorithm\SignatureAlgorithm. Overriding sign()/verify() in a subclass is the supported way to add logging, metrics, or to enforce a minimum key length — there is no middleware/hook layer.x values and compare them; if they differ, your signer and verifier are not using the same keypair and the algorithm class is fine.How can I help you explore Laravel packages today?