Skip to main content

Structured JSON

Structured JSON is the recommended request model for new JWTForge tests. It maps the request to the three JWT parts directly:

Request fieldJWT partPurpose
headerJWT headerHeader parameters such as alg, typ, kid, jku, and jwk
bodyJWT payloadStandard, OIDC, OAuth2, and custom claims
signatureJWT signatureNormal signing, unsigned token, or literal signature segment

JWTForge auto-detects structured JSON when the request contains any of these top-level fields:

  • header
  • body
  • signature

If none of those fields are present, JWTForge treats the request as the legacy flat claim model.

Minimal Request

{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"scope": "openid profile"
}
}

This generates a normally signed access token. JWTForge still adds default JWT claims such as iss, aud, exp, nbf, iat, and jti if they are not provided.

POST /token
Request
{
"header": {
"alg": "RS256",
"typ": "JWT",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"scope": "openid profile"
}
}
Response
No response yet
Decoded token
No token yet

Header Object

The header object controls JWT header parameters.

{
"header": {
"alg": "RS256",
"typ": "JWT",
"cty": "application/json",
"kid": "rsa-key-1",
"jku": "https://example.com/.well-known/jwks.json",
"jwk": {
"kty": "RSA",
"kid": "embedded-rsa-key",
"use": "sig",
"alg": "RS256",
"n": "test",
"e": "AQAB"
}
},
"body": {
"sub": "user123"
}
}
POST /token
Request
{
"header": {
"alg": "RS256",
"typ": "JWT",
"cty": "application/json",
"kid": "rsa-key-1",
"jku": "https://example.com/.well-known/jwks.json",
"jwk": {
"kty": "RSA",
"kid": "embedded-rsa-key",
"use": "sig",
"alg": "RS256",
"n": "test",
"e": "AQAB"
}
},
"body": {
"sub": "user123"
}
}
Response
No response yet
Decoded token
No token yet

Supported Header Fields

FieldDescriptionDefaultExample
algJWT signing algorithm advertised in the headerActive key algorithm, usually RS256"RS256", "ES256", "none", "nOne"
typToken type"JWT""JWT", "at+jwt"
ctyContent type for nested or typed JWT payloadsnone"JWT", "application/jwt"
kidKey identifierActive signing key ID"rsa-key-1"
jkuJWK Set URL referencenone"https://example.com/.well-known/jwks.json"
jwkEmbedded public JWK objectnone{"kty":"RSA","kid":"embedded-rsa-key"}
critCritical header parameter namesnone["exp-ext","custom-policy-id"]

Custom header parameters are allowed only when their names are listed in crit. JWTForge rejects a crit entry if the named header parameter is missing.

Unsupported Header Fields

FieldReasonBehaviorExample
x5uX.509 certificate URL headers are intentionally out of scopeRequest rejected with 400"https://example.com/cert.pem"
x5cX.509 certificate chain headers are intentionally out of scopeRequest rejected with 400["MIIB..."]
x5tX.509 certificate thumbprint headers are intentionally out of scopeRequest rejected with 400"abc123"

JWTForge rejects unsupported header fields with 400. That includes the certificate-chain header fields above.

Body Object

The body object becomes the JWT payload.

{
"body": {
"iss": "https://issuer.example.com",
"sub": "user123",
"aud": "https://api.example.com",
"scope": "openid profile email",
"roles": ["admin", "user"],
"tenant_id": "tenant-abc-123"
}
}
POST /token
Request
{
"body": {
"iss": "https://issuer.example.com",
"sub": "user123",
"aud": "https://api.example.com",
"scope": "openid profile email",
"roles": [
"admin",
"user"
],
"tenant_id": "tenant-abc-123"
}
}
Response
No response yet
Decoded token
No token yet

Supported Body Fields

FieldDescriptionDefaultExample
issIssuer identifierWorker URL"https://jwtforge.workers.dev"
subSubject identifier"user123""user123"
audAudience identifier"https://api.example.com""my-api"
expExpiration timestampCurrent time + 1 hour1735689600
nbfNot-before timestampCurrent time1735686000
iatIssued-at timestampCurrent time1735686000
jtiJWT IDRandom UUID"550e8400-e29b-41d4-a716-446655440000"
scopeOAuth2 scopesnone"openid profile email"
client_idOAuth2 client identifierAuto-generated when needed"test_app"
rolesApplication rolesnone["admin", "user"]
groupsUser groupsnone["engineering"]
nonceOIDC nonce for replay protectionnone"nonce-123"
nameFull nameGenerated in fake mode with profile scope"Jane Doe"
given_nameFirst nameGenerated in fake mode with profile scope"Jane"
family_nameLast nameGenerated in fake mode with profile scope"Doe"
preferred_usernamePreferred usernameGenerated in fake mode with profile scope"jane.doe"
profileProfile URLGenerated in fake mode with profile scope"https://example.com/users/jane"
pictureProfile image URLGenerated in fake mode with profile scope"https://example.com/avatar.jpg"
emailEmail addressGenerated in fake mode with email scope"jane@example.com"
email_verifiedEmail verification stateGenerated in fake mode with email scopetrue
addressOIDC address objectGenerated in fake mode with address scope{"country":"US"}
phone_numberPhone numberGenerated in fake mode with phone scope"+1-555-555-5555"
phone_number_verifiedPhone verification stateGenerated in fake mode with phone scopefalse
Custom fieldsAny additional claim accepted by the APInone"tenant_id": "tenant-abc-123"

Operational fields are not emitted as JWT claims. JWTForge removes fields such as:

  • mode
  • exclude
  • kty
  • response_type
  • grammar_category
  • malicious_category
  • vulnerability
  • signature

For clarity, prefer putting operational fields at the top level and claims inside body.

Signature Control

Omit signature to sign normally:

{
"body": {
"sub": "user123"
}
}
POST /token
Request
{
"body": {
"sub": "user123"
}
}
Response
No response yet
Decoded token
No token yet

Set signature to false to generate an unsigned token with a trailing dot:

{
"header": {
"alg": "none"
},
"body": {
"sub": "admin",
"roles": ["admin"]
},
"signature": false
}
POST /token
Request
{
"header": {
"alg": "none"
},
"body": {
"sub": "admin",
"roles": [
"admin"
]
},
"signature": false
}
Response
No response yet
Decoded token
No token yet

Pass a string to force a literal signature segment:

{
"body": {
"sub": "user123"
},
"signature": "literal-signature"
}
POST /token
Request
{
"body": {
"sub": "user123"
},
"signature": "literal-signature"
}
Response
No response yet
Decoded token
No token yet

Private-key signing from request payloads is reserved for a future extension.

Modes With Structured JSON

Modes work with structured JSON. JWTForge applies body transformations to body claims and header transformations to supported header fields.

Fake Mode

{
"mode": "fake",
"body": {
"sub": "user123",
"scope": "openid profile email"
}
}
POST /token
Request
{
"mode": "fake",
"body": {
"sub": "user123",
"scope": "openid profile email"
}
}
Response
No response yet
Decoded token
No token yet

Fuzz Mode

Fuzz mode is selective. It randomly mutates 1-3 available body claims per token, while provided supported header fields are fuzzed unless excluded. The signature field is not fuzzed by mode: "fuzz"; use signature: false, a literal signature string, or a vulnerability preset for signature-specific tests.

{
"mode": "fuzz",
"header": {
"alg": "RS256",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
POST /token
Request
{
"mode": "fuzz",
"header": {
"alg": "RS256",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
Response
No response yet
Decoded token
No token yet

Protect selected fields with exclude:

{
"mode": "fuzz",
"exclude": ["exp", "nbf", "iat", "header.alg"],
"header": {
"alg": "RS256"
},
"body": {
"sub": "user123"
}
}
POST /token
Request
{
"mode": "fuzz",
"exclude": [
"exp",
"nbf",
"iat",
"header.alg"
],
"header": {
"alg": "RS256"
},
"body": {
"sub": "user123"
}
}
Response
No response yet
Decoded token
No token yet

Malicious Mode

{
"mode": "malicious",
"malicious_category": "sql_injection",
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
POST /token
Request
{
"mode": "malicious",
"malicious_category": "sql_injection",
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
Response
No response yet
Decoded token
No token yet

Grammar Mode

{
"mode": "grammar",
"grammar_category": "vulnerable",
"header": {
"alg": "trigger",
"jku": "trigger",
"jwk": {}
},
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
POST /token
Request
{
"mode": "grammar",
"grammar_category": "vulnerable",
"header": {
"alg": "trigger",
"jku": "trigger",
"jwk": {}
},
"body": {
"sub": "user123",
"email": "user@example.com"
}
}
Response
No response yet
Decoded token
No token yet

Grammar mode resolves literal values and semantic templates before generating the token. Faker templates become realistic values such as emails, names, booleans, UUIDs, avatars, and usernames.

Vulnerability Presets

Structured JSON supports known JWT vulnerability presets.

{
"vulnerability": "rs_hs_confusion",
"body": {
"sub": "admin",
"roles": ["admin"]
}
}
POST /token
Request
{
"vulnerability": "rs_hs_confusion",
"body": {
"sub": "admin",
"roles": [
"admin"
]
}
}
Response
No response yet
Decoded token
No token yet

Supported presets:

PresetEffect
alg_noneSets header.alg to none and signature to false; use alg_none_variant for case variants such as None, NONE, or nOne
rs_hs_confusionSets header.alg to HS256
kid_traversalSets header.kid to a traversal-style value
jku_injectionSets header.jku to an attacker-style JWKS URL
embedded_jwkEmbeds the current public JWK in header.jwk

Presets apply before mode transformations, so use exclude when a mode should not mutate a preset field.

Example alg_none case variant:

{
"vulnerability": "alg_none",
"alg_none_variant": "nOne",
"body": {
"sub": "admin"
}
}
POST /token
Request
{
"vulnerability": "alg_none",
"alg_none_variant": "nOne",
"body": {
"sub": "admin"
}
}
Response
No response yet
Decoded token
No token yet

Response Types

Use response_type at the top level to generate access tokens, ID tokens, or both:

{
"response_type": "id_token token",
"body": {
"sub": "user123",
"scope": "openid profile email",
"nonce": "nonce-123"
}
}
POST /token
Request
{
"response_type": "id_token token",
"body": {
"sub": "user123",
"scope": "openid profile email",
"nonce": "nonce-123"
}
}
Response
No response yet
Decoded token
No token yet

Supported values:

Response typeDescriptionReturnsUse case
tokenAccess token onlyaccess_tokenAPI authorization and resource access
id_tokenID token onlyid_tokenUser authentication and identity verification
id_token tokenAccess token and ID tokenaccess_token + id_tokenOIDC hybrid-style testing
token id_tokenAccess token and ID token, alternative orderaccess_token + id_tokenCompatibility testing for order-insensitive clients

Key Type

Use kty at the top level to choose JWTForge's signing key type:

{
"kty": "EC",
"body": {
"sub": "user123"
}
}
POST /token
Request
{
"kty": "EC",
"body": {
"sub": "user123"
}
}
Response
No response yet
Decoded token
No token yet

Supported values:

Key typeSigning algorithmDescriptionDefaultExample
RSARS256RSA-2048 with SHA-256Yes"kty": "RSA"
ECES256ECDSA P-256 with SHA-256No"kty": "EC"

Full Example

curl -X POST https://your-worker.workers.dev/token \
-H "Content-Type: application/json" \
-d '{
"mode": "grammar",
"grammar_category": "valid",
"response_type": "id_token token",
"kty": "RSA",
"header": {
"alg": "RS256",
"typ": "JWT",
"cty": "application/json",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"scope": "openid profile email",
"aud": "my-client",
"nonce": "nonce-123",
"roles": ["admin"]
}
}'
POST /token
Request
{
"mode": "grammar",
"grammar_category": "valid",
"response_type": "id_token token",
"kty": "RSA",
"header": {
"alg": "RS256",
"typ": "JWT",
"cty": "application/json",
"kid": "rsa-key-1"
},
"body": {
"sub": "user123",
"scope": "openid profile email",
"aud": "my-client",
"nonce": "nonce-123",
"roles": [
"admin"
]
}
}
Response
No response yet
Decoded token
No token yet

Migration From Legacy JSON

Legacy flat JSON:

{
"sub": "user123",
"scope": "openid profile",
"header_alg": "none",
"sig": false
}

Structured JSON:

{
"header": {
"alg": "none"
},
"body": {
"sub": "user123",
"scope": "openid profile"
},
"signature": false
}

Migration rules:

  • Move JWT payload claims into body.
  • Move header_alg to header.alg.
  • Move header_kid to header.kid.
  • Replace sig: false with signature: false.
  • Keep operational fields such as mode, exclude, kty, response_type, and grammar_category at the top level.