JWT encoder
- Runs on your device
- Nothing uploaded
- Free, no sign-up
Signing uses Web Crypto HMAC-SHA-256 in this tab. The secret is never sent to ToolPetal.
What this encoder builds
It creates compact HS256 JWTs in the browser: base64url header, base64url payload, and an HMAC-SHA256 signature using your secret. Typical claims include iat and optional exp when you enable expiry.
This is for local testing and education. Shipping HS256 with a secret in a public SPA is usually the wrong threat model; prefer asymmetric algorithms on a server for production auth.
Worked example: signing a basic HS256 payload
Header {"alg":"HS256","typ":"JWT"}, payload {"sub":"123","name":"Ada"}, secret dev-only yields three dot-separated segments. Changing any character of the payload without resigning fails verification elsewhere.
Limits
Only HS256 is in scope here. No JWKS fetch, no RS256/ES256 signing, no encrypted JWE. Secrets typed on a shared screen are visible. Clear the form when finished.
Header and claims checklist
Keep alg as HS256 when using this page. Set typ to JWT for interoperability. In the payload, prefer standard claims (sub, iat, exp, aud) over inventing dozens of custom fields. Short TTLs reduce damage if a test token leaks from a screenshot.
Worked example: adding an exp claim and changing the secret
Header {"alg":"HS256","typ":"JWT"}, payload {"sub":"123","exp":1767225600}, secret test-secret. The token has three Base64URL parts separated by dots: header, payload, and the HMAC-SHA256 signature over the first two. Change one character of the secret and the third part changes completely while the first two stay the same. 1767225600 is 2026-01-01 00:00:00 UTC; convert your own expiry with the Unix timestamp converter.
Pitfalls
- Payloads are not encrypted. Anyone can Base64URL-decode the payload. Never put passwords or personal data in it.
- exp in milliseconds. JWT times are seconds since the epoch. A 13-digit value is milliseconds and will look like a date far in the future.
- Weak HS256 secrets. Short secrets can be brute-forced offline from any captured token. Use a long random secret, for example from the password generator.
Sign and verify HS256 JWTs in code
Each snippet signs a token with an expiry and then verifies it while pinning the algorithm, which blocks algorithm-swap attacks.
JavaScript: jose
import { SignJWT, jwtVerify } from "jose"; // npm i jose
const secret = new TextEncoder().encode("use-at-least-32-random-bytes-here!!");
const token = await new SignJWT({ sub: "user_123", role: "admin" })
.setProtectedHeader({ alg: "HS256", typ: "JWT" })
.setIssuedAt()
.setExpirationTime("1h")
.sign(secret);
const { payload } = await jwtVerify(token, secret, { algorithms: ["HS256"] });
console.log(token, payload.sub);
Python: PyJWT
from datetime import datetime, timedelta, timezone
import jwt # pip install PyJWT
secret = "use-at-least-32-random-bytes-here!!"
now = datetime.now(timezone.utc)
claims = {"sub": "user_123", "role": "admin", "iat": now, "exp": now + timedelta(hours=1)}
token = jwt.encode(claims, secret, algorithm="HS256")
decoded = jwt.decode(token, secret, algorithms=["HS256"]) # checks exp
print(token, decoded["sub"])
Go: github.com/golang-jwt/jwt/v5
package main
import (
"fmt"
"time"
"github.com/golang-jwt/jwt/v5" // go get github.com/golang-jwt/jwt/v5
)
func main() {
secret := []byte("use-at-least-32-random-bytes-here!!")
claims := jwt.MapClaims{
"sub": "user_123",
"iat": time.Now().Unix(),
"exp": time.Now().Add(time.Hour).Unix(),
}
signed, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString(secret)
if err != nil {
panic(err)
}
tok, err := jwt.Parse(signed, func(t *jwt.Token) (any, error) { return secret, nil },
jwt.WithValidMethods([]string{"HS256"}))
fmt.Println(signed, tok.Valid, err)
}
Each snippet was run on October 14, 2026 with jose 6.2.12, PyJWT 2.15.1 and golang-jwt v5.3.1.
JWT, JWS and JWA specs
- RFC 7519: JSON Web Token (JWT): registered claims such as iss, sub, aud, exp, iat
- RFC 7515: JSON Web Signature (JWS): compact serialization: header.payload.signature
- RFC 7518, section 3.2: HMAC with SHA-2: HS256, and why the key should be at least 256 bits
- RFC 8725: JWT Best Current Practices
- jose (JavaScript)
- PyJWT documentation
- Go: golang-jwt/jwt v5
FAQ
Does the JWT encoder upload secrets?
No. Signing runs locally. Still avoid pasting production secrets on shared machines.
Which algorithm?
HS256 (HMAC-SHA256) with a shared secret.
Can I decode JWTs here?
This page focuses on encoding. Use a decoder you trust, and never paste production tokens into random websites.
Why is HS256 risky in SPAs?
Anyone who can view the client can usually extract a hard-coded secret. Prefer server-side issuance with asymmetric keys for real auth.
Can I use tokens from this page in production?
Treat them as test tokens. For production, sign on your server with a secret that never touches a browser tab, and rotate it if it ever leaks.
Why does my server reject the token as expired right away?
Usually exp was set in milliseconds instead of seconds, or the server clock is off. Check exp with a timestamp converter and allow a small clock skew on the server.