How to create a signed JWT
- Choose the Algorithm. The
algfield in Header (JSON) follows the selection automatically. - Edit Payload (JSON). + iat now inserts the current time, + exp in 1 h sets an expiry one hour from now; both use seconds, as the JWT spec requires.
- For HS256, HS384 or HS512, enter the Secret and set Secret is to Text, Base64 or Hex. For the other algorithms, paste a private key or press Generate key pair.
- Copy token, or press Open in decoder to check it: the Decode & verify tab opens with the token and the verification key filled in.
The sample on load is a live HS256 token signed with the secret base64hub-demo-secret, so you can change one claim and watch the signature change with it.
Choosing an algorithm
| Algorithm | Key type | Who can verify | Signature in the token |
|---|---|---|---|
| HS256 | Shared secret (HMAC-SHA256) | Anyone holding the same secret | 43 characters |
| RS256, PS256 | RSA private key, 2048 bits or more | Anyone with the public key | 342 characters (RSA-2048) |
| ES256 | EC private key on P-256 | Anyone with the public key | 86 characters |
| EdDSA | Ed25519 private key | Anyone with the public key | 86 characters |
HS256 is the simplest choice when one service signs and verifies, such as a session token checked by the same backend. Once a second party has to verify, sharing an HMAC secret with them also lets them mint tokens, so switch to an asymmetric algorithm and publish only the public key. ES256 and EdDSA give much shorter tokens than RSA, which matters for cookies and headers. PS256 is RSA with the PSS padding scheme; pick it only if every verifier supports it.
none is in the list for testing parsers. A token with alg: none has an empty signature and any server that accepts it can be fooled by anyone, so the tool warns about it.
Keys this encoder accepts and generates
For signing it takes a PEM PKCS#8 key (BEGIN PRIVATE KEY), a PKCS#1 RSA PRIVATE KEY, a SEC1 EC PRIVATE KEY or a private JWK. To make test keys with OpenSSL instead of the generator:
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out rsa.pem
openssl genpkey -algorithm EC -pkeyopt ec_paramgen_curve:P-256 -out ec.pem
openssl genpkey -algorithm ed25519 -out ed.pem
openssl pkey -in ec.pem -pubout -out ec.pub
Generate key pair creates RSA-2048, P-256, P-384, P-521 or Ed25519 keys in the browser with WebCrypto, depending on the algorithm. You get the private key as PKCS#8 PEM, the public key as SPKI PEM, and a JWKS whose kid is the RFC 7638 thumbprint of the key. The JWKS is what you would serve from a jwks_uri for verifiers to download.
For HS256 the secret is used as bytes. If your server reads the secret from an environment variable and Base64-decodes it first, set Secret is to Base64 here as well, or the signatures will not match. A value from the JWT secret preset of the Base64 key generator is Base64URL and decodes to 32 bytes.
Which claims to put in the payload
A useful access token usually carries iss (who issued it), sub (who it is about), aud (which API it is for), iat and a short exp. Add jti if you need to revoke single tokens. Times are whole seconds since 1970; if you build them in JavaScript, use Math.floor(Date.now() / 1000), as Date.now() returns milliseconds.
The payload is signed, never encrypted. Anyone who sees the token can decode it with a JWT decoder, so leave out passwords, API keys and personal data you would not print in a log.
Signing a JWT in code
The format is simple enough to build by hand, which helps when debugging a library. This Node.js function produces exactly the token PyJWT’s jwt.encode(payload, secret, algorithm="HS256") gives for the same input:
const crypto = require("crypto");
const b64url = (obj) => Buffer.from(JSON.stringify(obj)).toString("base64url");
function signHS256(payload, secret) {
const input = b64url({ alg: "HS256", typ: "JWT" }) + "." + b64url(payload);
const sig = crypto.createHmac("sha256", secret).update(input).digest("base64url");
return input + "." + sig;
}
console.log(signHS256({ sub: "user-42", iat: 1767225600, exp: 1767229200 },
"replace-me-with-32-random-bytes!"));
It prints eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyLTQyIiwiaWF0IjoxNzY3MjI1NjAwLCJleHAiOjE3NjcyMjkyMDB9.F3xnXnXW8nUQy-Ay37DF626Lb5lHPKI3IFvqoEf3mTw. Enter the same header, payload and secret on this page and you get the same string. The tool compacts the JSON before signing, so indentation does not matter, but key order does, because the signature covers the exact bytes. In production use a maintained library; see Base64 in Node.js for the encoding side.
FAQ
How long should an HS256 secret be?
At least 256 bits, which is 32 random bytes. RFC 7518 requires a key at least as long as the hash output. A short password-like secret can be brute-forced offline from a single token, so generate one with the Base64 key generator.
Should I use HS256 or RS256?
HS256 when the same service issues and verifies tokens, because both sides share one secret. RS256, ES256 or EdDSA when other parties must verify, since they only get the public key and cannot mint tokens themselves.
Why does my private key show an error?
Password-protected keys (BEGIN ENCRYPTED PRIVATE KEY) are refused. Remove the passphrase with openssl pkey -in key.pem -out plain.pem and paste the result. The key type must also fit the algorithm: an EC key cannot sign RS256.
How do I add a kid to the token?
After Generate key pair, copy the kid from the JWKS output and add it to the header JSON, for example "kid": "…". Verifiers use it to pick the right key from their key set.