When to use JWTs (and when not to)
You cannot revoke a JWT before it expires. So the default lifetime is short (1 hour), and the opaque session is still the revoke target. If a JWT leaks, you must wait for it to expire or rotate
PYLON_JWT_SECRET (which invalidates every JWT).
Algorithm
- HS256 (HMAC-SHA256). Symmetric —
PYLON_JWT_SECRETis both signer and verifier. - No RS256, EdDSA, or asymmetric mode in
mint. Symmetric is correct when one Pylon binary creates and verifies the token. If multiple services must verify, share the secret over your existing secrets channel. - Standard JWT envelope:
<base64url-header>.<base64url-payload>.<base64url-signature>.
Endpoint
The JWT carries the caller’s identity at mint time:
sub (user id), tenant_id, and roles. A select-org after minting does not update the JWT. The next call to /api/auth/jwt reflects the new active tenant.
Claims
Bearer flow
A request can carry either an opaque session token or a JWT inAuthorization: Bearer. Pylon’s token resolver tries in this order:
- Admin token (constant-time compare)
pk.*API key- JWT (when
PYLON_JWT_SECRETis set AND the token’s 3-segment shape looks like a JWT) - Session token
alg=HS256, exp not in the past, and iss matches PYLON_JWT_ISSUER if set.
If verification fails, Pylon returns 401 with one of:
Configuration
Why PYLON_JWT_ISSUER is required
Without an issuer pin, any JWT signed with the same HS256 secret would verify, whatever its issuer. If you share PYLON_JWT_SECRET with another system (microservices, a third-party-token setup), a token issued for that system’s sub could sign into Pylon as that user. Set PYLON_JWT_ISSUER and require the iss claim to match to prevent this.
If PYLON_JWT_SECRET is set but PYLON_JWT_ISSUER is missing, Pylon refuses to verify any JWT (JWT_MISCONFIGURED). This is the safest default while the operator fixes the config.
Accepting JWTs minted elsewhere
/api/auth/jwt is for issuing tokens. To accept JWTs from another system (for example your Auth0 tenant or a homegrown SSO), use the jwt plugin instead:
Security guarantees
- HS256 + 256-bit secret —
openssl rand -hex 32produces a 32-byte secret. Anything shorter andPYLON_JWT_SECRETvalidates but the security argument weakens. alg=HS256enforced — thealg=noneattack and HS256/RS256 algorithm-confusion attack don’t apply; Pylon’s verify rejects any header wherealg != "HS256".- Issuer pin — refuse-to-validate when
PYLON_JWT_SECRETis set butPYLON_JWT_ISSUERis missing. - No revoke path — JWTs are valid until
exp. Keep lifetimes short (1 hour default). The underlying opaque session is still revocable via/api/auth/session//sessionsDELETE. - Claims captured at mint time —
tenant_idandrolessnapshot the session at issuance. Updating roles or running/select-orgdoesn’t invalidate existing JWTs.
Where to go next
- Sessions — the opaque-token default and why it’s usually the right answer
- API keys — long-lived server-to-server bearer tokens, also stateless but with explicit revoke
- Plugins / integrations — accepting JWTs from upstream IdPs