lcobucci/jwt · error · ConstraintViolation
Token signature mismatch
Error message
Token signature mismatch
What it means
SignedWith is a validation constraint that verifies a token's signature with a specific signer and key. It first checks that the token's 'alg' header matches the signer's algorithm, throwing 'Token signer mismatch' if not, then verifies the cryptographic signature. 'Token signature mismatch' means the alg header matched but the signature could not be verified with the given key.
Solutions
- Verify you pass the exact same key (or correct public key for asymmetric algorithms) used to sign the token
- Confirm the token was not modified in transit — copy it whole without added whitespace or newlines
- Check that signer algorithm matches how the token was actually signed (e.g. new Sha256() vs Sha384())
- Log/compare the token's alg header and your signer's algorithmId() to rule out algorithm drift
- Regenerate the token with the current key to confirm the verification path itself works
Example fix
// before
$validator->assert($token, new SignedWith(new Sha256(), InMemory::plainText('wrong-secret')));
// after
$validator->assert($token, new SignedWith(new Sha256(), InMemory::plainText('the-same-secret-used-to-sign'))); Defensive patterns
Strategy: try-catch
Validate before calling
if ($token instanceof UnencryptedToken && $token->headers()->get('alg') === 'HS256') {
// ensure your key material matches the one used at signing time before asserting
} Type guard
function isPlainSignedToken($token): bool { return $token instanceof UnencryptedToken && $token->signature()->hash() !== ''; } Try / catch
try {
$validator->assert($token, new SignedWith(new Sha256(), $key));
} catch (ConstraintViolation $e) {
// reject request / force re-authentication; log $e->getMessage()
} Prevention
- Store signing keys in one shared secret manager consumed by both signer and verifier
- Never log or truncate full tokens when copying between systems
- Rotate keys with an overlap window (SignedWithOneInSet) instead of hard swaps
- Pin signer algorithm on both sides and test with round-trip sign+verify in CI
When it happens
Trigger: Calling Validator::assert($token, new SignedWith($signer, $key)) where the token's signature does not verify against the provided key — e.g. signed with a different private key, key rotated since issuance, or token payload was modified after signing.
Common situations: Using a verification key that differs from the signing key; testing tokens from another environment (staging token verified in prod); key rotation without token invalidation handling; tampered or truncated tokens copied incorrectly (extra whitespace/quotes); HS256 secret mismatch between services.
Related errors
- Constraint Violation triggered by one of the following:\n
- No constraint given.
- Key provided is shorter than
- The JWT string is missing the Signature part
- The claim " " is a registered claim, another constraint…
AI-assisted analysis of lcobucci/jwt@375813049c (2026-09-14).
Data as JSON: /api/errors/128ece85179dcd77.
Report an issue: GitHub.
Appendix: source
Thrown at src/Validation/Constraint/SignedWith.php:29
final readonly class SignedWith implements SignedWithInterface
{
public function __construct(private Signer $signer, private Signer\Key $key)
{
}
public function assert(Token $token): void
{
if (! $token instanceof UnencryptedToken) {
throw ConstraintViolation::error('You should pass a plain token', $this);
}
if ($token->headers()->get('alg') !== $this->signer->algorithmId()) {
throw ConstraintViolation::error('Token signer mismatch', $this);
}
if (! $this->signer->verify($token->signature()->hash(), $token->payload(), $this->key)) {
throw ConstraintViolation::error('Token signature mismatch', $this);
}
}
}
View on GitHub (pinned to 375813049c)