About JWT Generator
This mints a signed JWT (HS256) from a header and payload you control, which is what you want when you need a token to test against rather than a real login flow. It is a development tool: the signing happens in a browser tab, so the tokens it produces belong in your test environment, not in production.
Two things are worth getting right in the payload. exp, iat and nbf are NumericDate values - seconds since the Unix epoch, not milliseconds - and iss and aud need to match whatever your verifier is configured to expect, or a perfectly signed token will still be rejected.
HS256 means a shared secret: the same value signs here and verifies in your service. That is fine for testing and fine when one party does both jobs, but it means anyone who can verify can also forge. If you are modelling an identity provider signing for multiple services, the production shape is RS256 with a private key held by the issuer.
Keep test tokens short-lived anyway. A long-expiry token generated for convenience has a way of ending up committed to a repository or pasted into a ticket, and it stays valid until it expires because JWTs have no built-in revocation.
The registered claims are worth knowing by name: iss (issuer), sub (subject - usually the user id), aud (audience - which API the token is for), exp (expiry), nbf (not before), iat (issued at) and jti (a unique token id). exp, nbf and iat are NumericDate values - seconds since the Unix epoch, not milliseconds - and mixing up the unit is one of the most common causes of a token that is somehow always expired or never valid.
Signing runs locally through the Web Crypto API, so nothing you type is transmitted.
How to use the JWT Generator
- Edit the payload claims - set sub, iss and aud to whatever your verifier expects.
- Set exp as seconds since the epoch, and keep the lifetime short.
- Enter the shared secret your service will verify with, decoding it from Base64 first if that is how it was issued to you.
- Generate and copy the token. Send it as an Authorization: Bearer header, and verify it once with the JWT verifier to confirm the round trip.
Examples
-
Payload
{"sub":"user-1","iat":1516239022} -
Secret
demo-secret
JWT Generator in code
The same operation this tool performs, in the languages you are most likely to need it.
const enc = new TextEncoder();
const b64url = (bytes) =>
btoa(String.fromCharCode(...new Uint8Array(bytes)))
.replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
const header = { alg: "HS256", typ: "JWT" };
const payload = {
sub: "user-123",
iss: "https://auth.example.com",
aud: "https://api.example.com",
iat: Math.floor(Date.now() / 1000),
exp: Math.floor(Date.now() / 1000) + 900, // 15 minutes, in SECONDS
};
const signingInput =
b64url(enc.encode(JSON.stringify(header))) + "." +
b64url(enc.encode(JSON.stringify(payload)));
const key = await crypto.subtle.importKey(
"raw", enc.encode(secret),
{ name: "HMAC", hash: "SHA-256" }, false, ["sign"]
);
const sig = await crypto.subtle.sign("HMAC", key, enc.encode(signingInput));
console.log(signingInput + "." + b64url(sig));
import jwt from "jsonwebtoken";
// HS256 - shared secret
const token = jwt.sign(
{ sub: "user-123", role: "admin" },
process.env.JWT_SECRET,
{
algorithm: "HS256",
expiresIn: "15m", // library converts to exp
issuer: "https://auth.example.com",
audience: "https://api.example.com",
}
);
// RS256 - what an identity provider actually does
const rsToken = jwt.sign({ sub: "user-123" }, privateKeyPem, {
algorithm: "RS256",
keyid: "2026-08-key-1", // becomes the kid header
expiresIn: "15m",
});
import jwt, time
now = int(time.time())
token = jwt.encode(
{
"sub": "user-123",
"iss": "https://auth.example.com",
"aud": "https://api.example.com",
"iat": now,
"exp": now + 900, # seconds, not milliseconds
},
key=secret,
algorithm="HS256",
)
print(token)
#!/usr/bin/env bash
b64url() { openssl base64 -A | tr '+/' '-_' | tr -d '='; }
HEADER=$(printf '%s' '{"alg":"HS256","typ":"JWT"}' | b64url)
NOW=$(date +%s)
PAYLOAD=$(printf '{"sub":"user-123","iat":%d,"exp":%d}' "$NOW" "$((NOW+900))" | b64url)
SIG=$(printf '%s' "$HEADER.$PAYLOAD" \
| openssl dgst -sha256 -hmac "$JWT_SECRET" -binary | b64url)
echo "$HEADER.$PAYLOAD.$SIG"
When you need this
- Producing a token with specific claims to test an authorisation rule.
- Creating a token for a role or scope you cannot easily obtain through the real login flow.
- Generating an already-expired or not-yet-valid token to test your error handling.
- Building a fixture token for an integration test suite.
- Checking that your verifier rejects a token signed with the wrong secret.
Common problems and what causes them
- exp set in milliseconds
- Using Date.now() instead of Math.floor(Date.now() / 1000) puts exp about 50,000 years in the future - or, if your verifier is strict about the value's range, makes it invalid outright. All three time claims are in seconds.
- Token verifies for you but not for your service
- Usually iss or aud. Many verifiers reject a token whose issuer or audience does not match their configuration, even with a valid signature. Match them exactly, and check whether your library requires aud at all.
- Secret used as text when the provider issued it as Base64
- Decode a Base64 secret to bytes before signing. Signing with the printable string produces a token your service cannot verify, and the error just says 'invalid signature'.
- Long-lived test tokens escaping into the world
- A token with a year-long expiry generated for convenience tends to end up in a commit, a Slack message or a ticket, and JWTs cannot be revoked. Keep test expiries in minutes.
- Putting sensitive data in the payload
- The payload is Base64URL-encoded, not encrypted, and readable by anyone holding the token. Keep personal data and internal secrets out of it - a user id is fine, an email address and a home address are not.
- Using HS256 where the signer and verifier are different parties
- A shared secret means every verifier can also forge tokens. Once more than one service verifies, move to RS256 so only the issuer holds the private key.
FAQ
- Can I use tokens from this tool in production?
- No. It is a development and testing tool: you would be typing a production signing secret into a browser tab, and the tokens carry claims you set by hand rather than ones your identity provider vouched for. Generate production tokens on your server.
- How do I set the expiry correctly?
- exp is seconds since the Unix epoch - Math.floor(Date.now() / 1000) + lifetimeInSeconds in JavaScript, int(time.time()) + seconds in Python. Passing milliseconds is the most common mistake with this claim.
- Which claims do I actually need?
- Technically none are mandatory, but a useful token generally has sub (who), exp (until when), and iss and aud if your verifier checks them. Add iat for auditability and jti if you need to identify or revoke individual tokens.
- Why does my service reject a token this tool says is valid?
- In order of likelihood: the secret differs (or is Base64 on one side); iss or aud does not match the verifier's configuration; a required claim is missing; or the verifier's allowlist does not include HS256.
- Can I generate an RS256 token here?
- This page signs with HS256. For RS256 you need an RSA private key - generate a pair with the RSA key pair generator and sign with your own tooling, since a private key is better kept out of a browser tab.
- RS256?
- This tool supports HS256; use your framework for asymmetric signing.
Related reading
- What is a JWT and how it works
- JWT header, payload and signature
- Common JWT security mistakes
- HS256 vs RS256
- JWT verifier check what you just made
- JWT decoder inspect the claims
- RSA key pair generator for RS256