Documentation

JWT Lab API

JWT Lab is intentionally not a complete OAuth authorization server. It is a test token mint: submit claims, receive a correctly signed RS256 JWT, and validate it against the published JWKS.

Issue a token

POST /api/token
Content-Type: application/json

{
  "preset": "delegated-agent",
  "claims": {
    "sub": "alice",
    "aud": "https://bank.example/mcp",
    "scope": "portfolio.read",
    "act": { "sub": "investment-advisor-agent" }
  },
  "expiresIn": 3600
}

Preset claims are applied first; explicit claims override them, and a claim set to null drops the preset default. The service manages iss, iat, exp and jti unless advanced: true is supplied.

Preset categories

Core — user and workload (client_credentials-shaped) access tokens. MCP — tokens audience-bound to an MCP server per the MCP authorization spec, with sub for the principal and client_id for the OAuth client. Delegation — RFC 8693 token-exchange output shapes with act chains: authorize on sub plus the outermost actor only.

Token profiles — spec-shaped tokens with their mandated typ headers set automatically:

basic-user           → typ: at+jwt            (RFC 9068 required: iss, exp, aud, sub, client_id, iat, jti)
spiffe-jwt-svid      → SPIFFE JWT-SVID        (sub is a SPIFFE ID; 5-minute lifetime; only sub, aud, exp required)
transaction-token    → typ: txntoken+jwt      (required: txn, sub, aud=trust domain, scope, req_wl)
id-jag               → typ: oauth-id-jag+jwt  (aud is the resource authorization server; client_id required)

Negative tests — validly signed tokens a conforming resource server must reject: expired and not-yet-valid (expect 401 invalid_token; introspection returns active: false). Other rejections (wrong iss, unbound aud, insufficient scope) are one claim edit away — set any claims you want in the payload. Edge cases — multi-valued aud arrays.

Discovery & validation

GET /.well-known/jwks.json
GET /.well-known/oauth-authorization-server
GET /.well-known/openid-configuration
GET /api/presets

Introspect a token

POST /api/introspect
Content-Type: application/x-www-form-urlencoded

token=eyJhbGciOiJSUzI1NiIs...

{"active": true, "sub": "alice", "exp": 1767225600, ...}

RFC 7662 token introspection: JWT Lab verifies the signature (RS256 against its published JWKS) and exp/nbf itself and returns an OAuth introspection response. Invalid, expired, tampered or malformed tokens return {"active": false} with no claim echo. The endpoint is unauthenticated: tokens are public-key-signed JWTs anyone holding them can decode, so introspection adds only the issuer's live validity opinion. Because JWT Lab is stateless, it can never report a valid token as revoked.

MCP

Connect an MCP client to <your-lab-host>/mcp. The remote server exposes five tools:

issue_token
introspect_token
list_presets
get_jwks
get_oauth_metadata

The MCP interface uses the same issuance engine as the UI and REST API, so an agent can mint a token and immediately inject it into whatever integration it is testing.

Hosting setup

JWT Lab is host-agnostic: it runs on any Node environment (serverless, container or plain process). Configure the environment before serving traffic:

npm install
npm run generate:key

# Set the printed JWT_PRIVATE_KEY_B64 value in the hosting environment.
# Also set:
JWT_ISSUER=https://your-lab.example.com
JWT_KID=jwt-lab-rs256-1

Important: keep the signing key stable. If an instance generates a different key, a token minted by one invocation might not validate against JWKS served by another.