</>DevTools

SGNJWT Token Generator

Generate signed JWT tokens with HS256 from a JSON payload and secret

HS256
HMAC-SHA256 (symmetric)
Recommended: a random string of at least 256 bits (32 bytes).

Signing JWTs with HS256: Building Tokens for Development and Testing

This tool takes a JSON payload and a secret and produces a JWT signed with HS256 (HMAC-SHA256). It's handy for poking an authenticated endpoint with curl during development, or for minting a token with an already-past exp to test expiry handling. What it is not for is equally clear: don't paste your production signing key into it.

What HS256 does

HS256 is symmetric. It computes HMAC-SHA256 over the string base64url(header) + "." + base64url(payload) using your secret, then base64url-encodes that result as the third segment. Only a party holding the same secret can produce the same signature, so the verifier must know the identical secret the issuer used.

That's also HS256's limitation. If several services need to verify tokens, all of them must share the one secret, and a leak anywhere makes forgery possible everywhere. With multiple verifiers, RS256 or ES256 — sign with a private key, verify with a public one — is the right fit.

AlgorithmKey modelFits when
HS256One shared secretIssuer and verifier are the same service
RS256Private key signs, public key verifiesMany services verify; OIDC/JWKS setups
ES256Elliptic curve, shorter signaturesSame shape as RS256 but you want smaller tokens

How to use it

  1. Write the JSON payload. Using standard claims like sub and exp maximizes library compatibility.
  2. Enter a secret. Any string works for testing, but read the key-length section below.
  3. Copy the generated token into an Authorization: Bearer <token> header.
  4. To test expiry handling, set exp to a past Unix timestamp to produce an already-expired token.

Getting exp and iat right

exp and iat are Unix timestamps in seconds, UTC. JavaScript's Date.now() returns milliseconds, so divide by 1000 and floor it. Forgetting that makes exp a thousand times too large, setting expiry tens of thousands of years out — an effectively immortal token, and a genuinely common bug.

Computing timestamps correctly
const now = Math.floor(Date.now() / 1000);

const payload = {
  sub: "user-1234",
  iat: now,
  exp: now + 60 * 15,   // 15 minutes from now
  // for expiry testing:
  // exp: now - 60,     // one minute ago = already expired
};

Why secret length matters

HMAC-SHA256's strength depends on the entropy of your secret. Using a short word like 'secret' or 'mysecretkey' means an attacker who captures a single token can recover the key by dictionary attack. With the key, they can sign any payload they like — including one claiming administrator rights.

RFC 7518 requires an HS256 key at least as long as the hash output, i.e. a minimum of 256 bits (32 bytes). The usual practice is to generate 32 random bytes, render them as Base64, and keep that in an environment variable.

Generating a strong enough key
# Node.js
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"

# OpenSSL
openssl rand -base64 32

Decisions to make when issuing

  • Lifetime: short access tokens (5–30 minutes) refreshed by a refresh token is the standard shape.
  • aud: if several APIs trust the same issuer, set aud so a token can't be replayed against the wrong API.
  • iss: checking the issuer at verification time blocks tokens from another system leaking in.
  • jti: for one-time tokens or forced revocation, include a unique ID the server can track.
  • How much authorization data: one role is fine, but a fine-grained permission list bloats the token and delays permission changes until expiry.

Frequently Asked Questions

Can I use my production secret here?
Don't. Signing runs entirely in your browser and the key isn't transmitted, but a production signing key is a top-tier secret — leaking it lets anyone impersonate any user. Making a habit of pasting such values into browser inputs is itself the risk. Test with a development or throwaway key.
My generated token is rejected by the server.
Check in this order. Does the server's secret match exactly, including stray whitespace or newlines? Does the server allow HS256 — many are configured for RS256 only? Has exp already passed? Does the server verify iss or aud that your payload lacks or sets differently? And is the Authorization header exactly 'Bearer ' plus the token?
Are there limits on payload contents?
Formally, any valid JSON. Practically there are two constraints: anyone can read it, so no secrets belong there; and the token rides in a header on every request, so size is a real cost. Exceeding a server's header limit (typically 4–8KB) causes the request itself to be rejected.
Can it produce RS256 tokens?
This tool only supports HS256. RS256 needs an RSA key pair, so generating keys with the RSA Key Generator and signing with a server-side library (jose, jsonwebtoken) is closer to how you'd actually run it.
How do I deliberately create an expired token?
Set exp to a Unix timestamp smaller than now — subtracting 3600 gives a token that expired an hour ago. It's useful for confirming that your client correctly follows its refresh flow on a 401.
Can I change typ or kid in the header?
This tool emits the standard {"alg":"HS256","typ":"JWT"} header. For tokens that include a kid you'll need a server-side library with header options. When testing a verifier, though, it's worth trying tampered kid values to confirm it defends against path traversal.

💡 Note: Don't leave test tokens in version control or issue trackers. Long-lived tokens committed to a repository are a recurring source of real incidents.

🔗Related Tools🔐 Crypto / Security