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.