Encode and sign JSON Web Tokens client-side
The signing operation runs in this browser page. Use throwaway test material only.
A verifier is only trustworthy if you have seen it say no. Real tokens are hard to produce in a bad state, so you build them: one whose exp is in the past, one whose nbf is in the future, one with the wrong aud or iss, one signed with the wrong key, and one signed with HS256 sent to an endpoint that expects an asymmetric algorithm. That last case is the algorithm-confusion test described in RFC 8725: a verifier must accept only the algorithms it expects, not whatever the token header names. This tool signs only HS256 and HS512 and always writes the algorithm you selected into the header, so it cannot make an alg: none token. Each of the other cases takes one edit of the payload shown below.
Use the default header, paste this payload, set the algorithm to HS256 and use the synthetic secret below. The output must be byte for byte the token shown. If it is not, a field differs; JSON key order and whitespace matter because they change the signed bytes.
| Field | Value |
|---|---|
| Secret (32 bytes, synthetic) | synthetic-test-secret-key-32-byt |
| Header | {"alg":"HS256","typ":"JWT"} |
| Payload | {"iss":"https://issuer.test.example","aud":"https://api.test.example","sub":"test-user-123","iat":1700000000,"nbf":1700000000,"exp":1700003600} |
| Token | eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci50ZXN0LmV4YW1wbGUiLCJhdWQiOiJodHRwczovL2FwaS50ZXN0LmV4YW1wbGUiLCJzdWIiOiJ0ZXN0LXVzZXItMTIzIiwiaWF0IjoxNzAwMDAwMDAwLCJuYmYiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDAwMzYwMH0.HEW0mzlWVnHQAHy7V6Zs1mbzit6L0vCgasILJY5LvWI |
Check the signature outside the browser. Both commands print HEW0mzlWVnHQAHy7V6Zs1mbzit6L0vCgasILJY5LvWI:
TOKEN='eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJodHRwczovL2lzc3Vlci50ZXN0LmV4YW1wbGUiLCJhdWQiOiJodHRwczovL2FwaS50ZXN0LmV4YW1wbGUiLCJzdWIiOiJ0ZXN0LXVzZXItMTIzIiwiaWF0IjoxNzAwMDAwMDAwLCJuYmYiOjE3MDAwMDAwMDAsImV4cCI6MTcwMDAwMzYwMH0.HEW0mzlWVnHQAHy7V6Zs1mbzit6L0vCgasILJY5LvWI'
printf '%s' "${TOKEN%.*}" \
| openssl dgst -sha256 -hmac 'synthetic-test-secret-key-32-byt' -binary \
| basenc --base64url | tr -d '='
node -e "const c=require('node:crypto');
const t=process.argv[1], i=t.slice(0,t.lastIndexOf('.'));
console.log(c.createHmac('sha256','synthetic-test-secret-key-32-byt').update(i).digest('base64url'))" "$TOKEN"RFC 7519 defines exp, nbf and iat as NumericDate values: seconds since 1970-01-01T00:00:00Z UTC, not milliseconds. The sample uses iat and nbf 1700000000 (2023-11-14T22:13:20.000Z) and exp 1700003600 (2023-11-14T23:13:20.000Z), a one-hour lifetime. In JavaScript, write Math.floor(Date.now() / 1000); a raw Date.now() is a thousand times too large.
| Claim | To test rejection | Rule (RFC 7519) |
|---|---|---|
exp | Set it to a past second, such as 1699999999 | The current time must be before exp (section 4.1.4) |
nbf | Set it to a future second | The current time must be at or after nbf (section 4.1.5) |
iat | Set it far in the future | Informational; apply your own age limit (section 4.1.6) |
RFC 7519 allows a small leeway for clock skew; if your verifier has one, test just outside it, not far outside.
RFC 7518 section 3.2 says a key at least as large as the hash output must be used with HS256, which means 256 bits (32 bytes) or more. This encoder only requires a non-empty secret, so it will sign with a short one; do not read that as approval. Use at least 32 random bytes whenever you are testing a verifier's key handling, and "Generate Test Secret" fills a throwaway value.
The first two segments are Base64URL-encoded JSON. Anyone holding the token can read them, with no key; the secret only lets a verifier detect changes. Do not put passwords, personal data or anything confidential in a payload. To see this, paste the token into the JWT decoder, which reads claims without verifying the signature. Our JWT vulnerabilities article covers the common implementation mistakes.
Signing happens in this page, but a production signing key pasted into any web form can be exposed to browser extensions, screen shares or clipboard history. If a key has been pasted anywhere it should not be, rotate it. Create invented claims and a throwaway secret, and point them at a test environment.