Verificador de Firma JWT
Verifica firmas de tokens JWT con validación de clave y comprobación de expiración
What Is JWT Verification?
JWT verification is the process of confirming that a JSON Web Token is authentic, unaltered, and
still valid for use. Unlike decoding — which simply parses the Base64URL-encoded segments — verification
cryptographically validates the token's signature against a known key and checks that time-based claims
(such as exp, nbf, and iat) fall within acceptable bounds.
A decoded token tells you what it contains; a verified token tells you that those contents
are trustworthy. RFC 7519 (JSON Web Token) defines the claim semantics, while RFC 7515
(JSON Web Signature) specifies how signatures are computed and validated. Every API endpoint, OAuth
resource server, or microservice that accepts JWTs must perform full verification before acting on the
token's claims — skipping any step opens the door to token forgery, replay attacks, and privilege
escalation.
Signature Validation Algorithms
The signature is the core trust anchor of a JWT. It is computed over the concatenation of the
Base64URL-encoded header and payload (header.payload) using the algorithm declared in
the alg header parameter. Verification reverses this process: the verifier recomputes
the expected signature using the same algorithm and the appropriate key, then compares it to the
signature segment of the token. If they do not match, the token has been tampered with or was
signed by an untrusted party.
Symmetric algorithms (HMAC family) use a single shared secret for both signing and verification.
HS256 applies HMAC-SHA-256, HS384 uses HMAC-SHA-384, and HS512
uses HMAC-SHA-512. These are appropriate when both issuer and verifier share a secret — typically
in monolithic applications or tightly coupled services. The shared secret must be at least as long
as the hash output (32 bytes for HS256) to resist brute-force attacks.
Asymmetric algorithms separate signing from verification using key pairs. RS256,
RS384, and RS512 use RSA PKCS#1 v1.5 signatures with SHA-256, SHA-384,
and SHA-512 respectively. PS256, PS384, and PS512 use the
more modern RSA-PSS padding scheme. ES256, ES384, and ES512
employ ECDSA with the P-256, P-384, and P-521 curves. Asymmetric verification requires only the
public key, making it ideal for distributed architectures where multiple services verify tokens
issued by a central identity provider. The private key never leaves the issuer.
A critical security rule: the verifier must never trust the alg header
blindly. An attacker could change alg to "none" or switch from RS256 to
HS256 (using the public key as the HMAC secret). Verifiers must maintain an explicit allowlist of
accepted algorithms and reject tokens with unexpected alg values before attempting
any cryptographic operation.
Expiration and Time-Based Claim Validation
Time-based claims control a token's temporal validity window. The exp (expiration time)
claim, defined in RFC 7519 Section 4.1.4, specifies the NumericDate after which the token must be
rejected. Verification compares exp against the current UTC time: if
currentTime >= exp, the token is expired. Short-lived tokens (5–15 minutes) limit the
damage window if a token is stolen, while refresh token rotation provides seamless re-authentication.
The nbf (not before) claim, defined in RFC 7519 Section 4.1.5, sets the earliest time
at which the token becomes valid. If currentTime < nbf, the verifier must reject the
token. This is useful for pre-issuing tokens that activate at a future time — for example, granting
access that begins at a scheduled deployment window.
The iat (issued at) claim records when the token was created. While not strictly a
validation requirement, verifiers can use iat to reject tokens that are unreasonably
old (e.g., issued more than 24 hours ago) as a defense against long-lived stolen tokens, even if
exp has not yet passed.
Clock skew is an unavoidable challenge in distributed systems. If the issuer's clock is 30 seconds ahead of the verifier's clock, tokens will appear expired prematurely. Standard practice is to apply a configurable clock tolerance (typically 30–60 seconds) that extends the acceptance window on both ends. RFC 7519 acknowledges this in Section 4.1.4: implementations "MAY provide for some small leeway, usually no more than a few minutes, to account for clock skew."
Audience and Issuer Verification
The aud (audience) claim identifies the intended recipients of a token. RFC 7519
Section 4.1.3 mandates that a verifier must reject a token unless it identifies itself in the
aud claim. For example, if an API server's audience identifier is
"https://api.example.com", it must reject tokens whose aud claim does
not include that value. The aud field can be either a single string or an array of
strings, covering multi-audience scenarios where one token grants access to several services.
The iss (issuer) claim identifies who issued the token. Verifiers must check that
iss matches a known, trusted issuer — typically the URL of the OAuth authorization
server or identity provider. Accepting tokens from an untrusted issuer means any attacker running
their own identity server could forge accepted tokens. In OpenID Connect flows, the issuer URL
is also used to discover the JWKS (JSON Web Key Set) endpoint for retrieving public verification keys.
Combining iss + aud verification ensures that a token was created by
a trusted party specifically for the verifying service. Without audience validation, a token
intended for Service A could be replayed against Service B — a confused deputy attack. Without
issuer validation, an attacker's self-issued token would be accepted.
Security Considerations During Verification
Constant-time comparison is essential when verifying HMAC signatures. A naive byte-by-byte
comparison that exits early on the first mismatch leaks timing information, allowing an
attacker to iteratively guess the correct signature one byte at a time. Cryptographic libraries
provide constant-time equality functions (e.g., crypto.timingSafeEqual in Node.js)
that compare the full length regardless of content.
Key management directly impacts verification security. HMAC secrets must be generated with
sufficient entropy (at least 256 bits for HS256) and stored securely — never in source code or
client-side bundles. RSA keys should be at least 2048 bits (4096 preferred for long-lived keys).
ECDSA P-256 keys provide equivalent security to 3072-bit RSA with shorter signatures and faster
verification. Key rotation should be supported: verifiers can check the kid (key ID)
header parameter against a JWKS endpoint to select the correct verification key, allowing seamless
rotation without downtime.
Token replay is mitigated by short expiration times, audience binding, and one-time-use tokens
(using the jti — JWT ID — claim with a server-side revocation list). For high-security
scenarios, sender-constrained tokens bind the token to a specific TLS certificate via DPoP
(Demonstration of Proof-of-Possession, RFC 9449), preventing stolen tokens from being used on
different connections.
Never verify only the signature without also validating claims. A properly signed token can still be expired, intended for a different audience, or issued by an untrusted party. Full verification requires: (1) algorithm allowlist check, (2) signature validation, (3) expiration check, (4) not-before check, (5) issuer validation, (6) audience validation. Skipping any step leaves a vulnerability.
Code Examples
JWT verification with signature and claims validation (Node.js)
import { createHmac, timingSafeEqual } from 'crypto';
function base64UrlDecode(str) {
const base64 = str.replace(/-/g, '+').replace(/_/g, '/');
const padded = base64 + '='.repeat((4 - base64.length % 4) % 4);
return Buffer.from(padded, 'base64');
}
function verifyJwt(token, secret, options = {}) {
const { allowedAlgorithms = ['HS256'], audience, issuer, clockToleranceSec = 30 } = options;
const parts = token.split('.');
if (parts.length !== 3) {
return { valid: false, error: 'Invalid token structure: expected 3 parts' };
}
// Step 1: Decode header and validate algorithm
const header = JSON.parse(base64UrlDecode(parts[0]).toString('utf8'));
if (!allowedAlgorithms.includes(header.alg)) {
return { valid: false, error: `Algorithm "${header.alg}" not in allowlist` };
}
// Step 2: Verify signature using constant-time comparison
const signingInput = parts[0] + '.' + parts[1];
const expectedSig = createHmac('sha256', secret).update(signingInput).digest();
const actualSig = base64UrlDecode(parts[2]);
if (expectedSig.length !== actualSig.length ||
!timingSafeEqual(expectedSig, actualSig)) {
return { valid: false, error: 'Signature verification failed' };
}
// Step 3: Decode payload and validate time claims
const payload = JSON.parse(base64UrlDecode(parts[1]).toString('utf8'));
const now = Math.floor(Date.now() / 1000);
if (payload.exp && now >= payload.exp + clockToleranceSec) {
return { valid: false, error: 'Token has expired' };
}
if (payload.nbf && now < payload.nbf - clockToleranceSec) {
return { valid: false, error: 'Token is not yet valid (nbf)' };
}
// Step 4: Validate issuer and audience
if (issuer && payload.iss !== issuer) {
return { valid: false, error: `Issuer mismatch: expected "${issuer}"` };
}
if (audience) {
const audArray = Array.isArray(payload.aud) ? payload.aud : [payload.aud];
if (!audArray.includes(audience)) {
return { valid: false, error: `Audience "${audience}" not found in token` };
}
}
return { valid: true, header, payload };
}
// Usage
const token = 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJ1c2VyMTIzIiwiZXhwIjoxNzE4MDAwMDAwLCJhdWQiOiJhcGkuZXhhbXBsZS5jb20iLCJpc3MiOiJhdXRoLmV4YW1wbGUuY29tIn0.signature';
const result = verifyJwt(token, 'your-256-bit-secret', {
allowedAlgorithms: ['HS256'],
audience: 'api.example.com',
issuer: 'auth.example.com',
clockToleranceSec: 60
});
console.log(result);
// → { valid: true, header: { alg: 'HS256' }, payload: { sub: 'user123', ... } }Standards & Specifications
- RFC 7519 — JSON Web Token (JWT) — defines claim semantics including exp, nbf, iat, aud, iss, and verification requirements
- RFC 7515 — JSON Web Signature (JWS) — specifies signature computation and validation procedures for signed JWTs
- RFC 7518 — JSON Web Algorithms (JWA) — defines cryptographic algorithms (HS256, RS256, ES256, etc.) for JWS signature verification
- RFC 9449 — DPoP (Demonstration of Proof-of-Possession) — sender-constrained tokens to prevent replay attacks
Preguntas Frecuentes
What's the difference between decoding and verifying a JWT?
Decoding simply reveals the JWT's contents by Base64URL-decoding the header and payload. Verification checks the signature using the secret or public key to ensure the token is authentic and hasn't been tampered with. This tool verifies signatures - it validates that the token is genuine and trustworthy.
Should I use this tool with production secrets?
No, this tool is for testing and development only. Never enter production secrets or private keys in browser-based tools. Always verify JWTs on your secure backend server in production environments where secrets can be properly protected.
What's the difference between HMAC and RSA verification?
HMAC algorithms (HS256, HS384, HS512) use a shared secret key for both signing and verification. RSA algorithms (RS256, RS384, RS512) use public/private key pairs - tokens are signed with the private key and verified with the public key. RSA is safer for distributed systems since you can share the public key without compromising security.
Why does verification fail with 'Invalid signature'?
This error means the signature doesn't match the token's content. Common causes: using the wrong secret/key, the token was tampered with, or the algorithm doesn't match the one used to sign the token. Make sure you're using the exact same secret and algorithm that was used to create the token.
What happens if my token is expired?
The verifier checks the 'exp' (expiration) claim and will fail verification if the current time is past the expiration time. This is a security feature - expired tokens should not be trusted. You'll see a clear error message indicating the token has expired.
What is the 'nbf' (not before) claim?
The 'nbf' claim specifies a time before which the token should not be accepted. If you try to verify a token before its 'nbf' time, verification will fail. This is useful for tokens that should only become valid at a future time.
Can I verify tokens without the secret key?
No, you need the secret key (for HMAC algorithms) or the public key (for RSA algorithms) to verify signatures. Without the correct key, verification will always fail. This is by design - only parties with the key can verify token authenticity.
Is my data sent to a server?
No, all JWT verification happens in your browser. Your tokens, secrets, and keys never leave your device. However, remember this is for testing only - never use production secrets in any browser-based tool.