CruxDevCruxDev Tools Prompts
security Utility • Zero-Server Privacy • 100% Client-Side

JWT Generator (HS256)

Sign test JWTs locally with HS256 via Web Crypto — custom claims, expiry, and issuer, nothing leaves your device.

Overview

Builds and signs a real HS256 JSON Web Token in your browser, with fields for subject, issuer and lifetime plus a free-form JSON object for any custom claims you need. Signing uses Web Crypto HMAC-SHA-256, the iat and exp claims are filled in for you as Unix timestamps, and the secret never leaves the page.

How It Works

Fill in the subject, issuer, an expiry in minutes and any custom claims as a JSON object, then press Sign JWT. Unlike a live formatter this runs on demand, so an incomplete edit never produces a half-built token. The tool assembles the header and payload, base64url-encodes them, signs the joined string, and appends the signature to give the finished three-part token. If the custom claims box is not valid JSON, it reports that specifically instead of emitting a token with the claims silently dropped.

Step-by-Step Usage Guide

  1. Set the subject to whatever your application treats as the user identifier, and the issuer to the URL or name your API expects in the iss claim.
  2. Set the lifetime in minutes. The tool converts it to seconds and adds it to the current time to produce exp, since JWT timestamps are seconds, not milliseconds.
  3. Add any custom claims as a JSON object — roles, tenant ids, scopes. Remember these are merged last and will override a generated claim of the same name.
  4. Press Sign JWT with the secret your API verifies against, then copy the token into an Authorization: Bearer header to exercise your endpoint.

Technical Specifications & Standards

A JWT is three base64url segments joined by dots: header, payload, signature. The header here is fixed to { alg: 'HS256', typ: 'JWT' }. The payload starts with the registered claims — sub, iss, iat set to the current Unix second, and exp set to that plus your lifetime in minutes converted to seconds — and then your custom JSON is merged over it with Object.assign. The merge order matters and is a genuine gotcha: because your object is applied last, a custom claim named exp, sub, iss or iat overrides the generated one, which is useful for deliberately minting an already-expired token to test rejection paths, and surprising if you did not intend it. The signing input is the header and payload segments joined by a dot; that exact string is signed with HMAC-SHA-256 through crypto.subtle.sign and the MAC is base64url-encoded as the third segment. Base64url differs from standard base64 in using - and _ in place of + and / and dropping the = padding, which is why JWTs survive being placed in URLs and headers. Note what HS256 implies: it is symmetric, so the same secret both mints and verifies tokens. Anyone holding it can forge a token for any subject, which is why production systems that must distribute verification keys use an asymmetric algorithm such as RS256 or ES256 instead.

Targeted Use Cases

  • Testing authentication middleware with a token whose claims you control exactly, rather than going through a full login to obtain one.
  • Producing fixtures for integration tests, including deliberately expired tokens made by overriding exp with a timestamp in the past.
  • Reproducing a claim-shape bug by minting a token with the precise roles or scopes a failing request carried.
  • Checking that your verifier actually rejects what it should — wrong issuer, missing subject, or an expiry that has already passed.

Notes & Gotchas

  • Use development secrets only. HS256 is symmetric, so the secret that signs is the secret that verifies, and pasting a production signing key anywhere is equivalent to handing over the ability to impersonate any user.
  • Keep exp in seconds. Passing a JavaScript Date.now() value into a custom exp claim sets an expiry roughly fifty thousand years out, and libraries will happily accept it.
  • Be deliberate about the merge. Custom claims are applied over the generated ones, so naming one exp or sub replaces the field the form produced.
  • Do not treat a JWT as private. The header and payload are base64url, not encrypted — anyone holding the token can read every claim, so never put secrets in one.

Frequently Asked Questions

Which algorithms are supported?

HS256 only. The header is fixed to { alg: 'HS256', typ: 'JWT' } and signing uses HMAC-SHA-256. Asymmetric algorithms like RS256 and ES256 need a key pair rather than a shared secret, which is a different workflow.

Can I create an expired token to test rejection?

Yes. Add an exp claim with a past Unix timestamp in the custom claims box. Because custom claims are merged after the generated ones, your value replaces the calculated expiry.

Is the token encrypted?

No, and no JWT of this kind is. The header and payload are base64url-encoded, which is encoding rather than encryption — anyone with the token can decode and read them. The signature proves the token was not altered, not that its contents are hidden.

Does my secret get sent to a server?

No. The secret is imported into Web Crypto and used to sign locally in your browser, with no network request. Even so, treat any browser tool as unsuitable for real production signing keys.