Skip to content
100% in your browser. Nothing you paste is uploaded — all processing runs locally. Read more →

Verify JWT signature (HMAC)

Paste a JWT and the secret it was signed with. The Web Crypto API runs the HMAC verification locally — instant, private, no upload.

 
 
⚠ Use this for testing only. Don't sign production tokens with secrets you've pasted into a web tool.
 
🔒 100% client-side · uses your browser's Web Crypto API · no upload

Why signature verification matters

A JWT's first two segments (header, payload) are just base64-encoded JSON. Anyone with a JWT can decode them — that's not a security hole, it's the design. The signature is what makes the token trustworthy: only someone with the correct key can produce a valid signature for a given header+payload.

A receiver who skips signature verification is treating attacker- controlled JSON as authoritative. That's a critical bug. Always verify.

Algorithms this tool supports

AlgorithmHashUse case
HS256SHA-256Most common; symmetric, secret can be any UTF-8 string ≥ 256 bits
HS384SHA-384Higher security margin; rare
HS512SHA-512Highest HMAC security; rare

What "valid" means

A "VALID" result means: the JWT was signed by someone who knew the secret you provided, and neither the header nor the payload has been modified since signing.

It does not mean:

A complete authentication check verifies signature and claims and business rules. Most JWT libraries do this for you when you call jwt.verify(token, secret) with the right options.

Common verification failures

Programmatic equivalents

// Node.js with jose
import { jwtVerify } from "jose";
import { TextEncoder } from "util";
const secretKey = new TextEncoder().encode(secret);
const { payload } = await jwtVerify(token, secretKey, {
  algorithms: ["HS256"],
});

// Python with PyJWT
import jwt
payload = jwt.decode(token, secret, algorithms=["HS256"])

// Go with golang-jwt
import "github.com/golang-jwt/jwt/v5"
token, err := jwt.Parse(tokenStr, func(t *jwt.Token) (any, error) {
    if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
        return nil, fmt.Errorf("unexpected method")
    }
    return []byte(secret), nil
})

Always specify expected algorithms

Every snippet above passes algorithms: ["HS256"] (or similar) explicitly. Never let the JWT's own header decide which algorithm to verify with. A famous class of bugs (CVE-2015-2951 and friends) was: server signs with RS256, attacker forges a JWT with alg: HS256 and the public key as the HMAC secret. The server, trusting the alg in the header, used the public key to verify the HMAC — and accepted the attacker's forged token.

This tool's UI doesn't have that bug because the verifier is HMAC-only and reads the alg from the JWT only to pick the right hash size — but in your own code, always pin the algorithm.

FAQ

Why HMAC only?

HMAC (HS256/384/512) uses a single shared secret. Verification needs the secret and the JWT — that's it. RSA / ECDSA / EdDSA need a public key in PEM or JWK format, plus algorithm-specific Web Crypto setup. The MVP supports HMAC because it's the most common in symmetric internal-API setups.

How does HMAC verification work?

The verifier computes HMAC-SHA256(header + '.' + payload, secret) and compares it byte-for-byte with the JWT's signature segment. Match = valid. Mismatch = either wrong secret, or someone tampered with the header or payload.

What if my JWT uses RS256?

This tool will tell you the algorithm isn't supported. For RS256/384/512 or ES256/384/512 verification, use a library on the server: jose (JS), PyJWT (Python), github.com/golang-jwt/jwt (Go), etc. We're considering adding RSA verification — vote via the email signup.

Is the secret stored anywhere?

No. The secret is held in JavaScript memory just long enough to import into the Web Crypto HMAC key, run verify, and discard. Nothing is sent over the network or persisted. Verify by checking DevTools → Network and Storage tabs after pasting.