Definition

JSON Web Token (JWT, pronounced "jot") is an open standard defined in RFC 7519 for creating compact, URL-safe tokens that carry claims between parties. A JWT consists of three Base64url-encoded parts separated by dots: a header specifying the signing algorithm and token type, a payload containing the claims (assertions about an entity and additional metadata), and a signature that cryptographically verifies the token's integrity and authenticity. The self-contained nature of JWTs means that all information needed to validate the token and make authorization decisions is embedded within the token itself.

JWTs enable stateless authentication because the server can validate the token using only a cryptographic key, without consulting a session store or database. This property is particularly valuable in distributed systems and microservice architectures where maintaining centralized session state would create a bottleneck and single point of failure. A valid JWT carries the complete authorization context -- user identity, roles, permissions, expiration -- in a portable, verifiable package.

The JWT ecosystem includes two related specifications: JSON Web Signature (JWS, RFC 7515) for signed tokens that provide integrity and authenticity, and JSON Web Encryption (JWE, RFC 7516) for encrypted tokens that additionally provide confidentiality. Most practical JWT deployments use JWS, where the payload is readable by anyone but cannot be modified without invalidating the signature. JWE is used when the claims themselves contain sensitive information that must be hidden from intermediaries.

Token Structure

A JWT is composed of three parts, each Base64url-encoded and concatenated with dots:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJ1c2VyOjQyIiwicm9sZXMiOlsiYW5hbHlzdCJdLCJleHAiOjE3MDY3NDU2MDB9.HMAC_SHA256_SIGNATURE
|___________________________________|.|___________________________________________________________________________________|.|__________________________|
             Header                                                     Payload                                                    Signature
PartContentEncodingPurpose
Header{"alg": "HS256", "typ": "JWT"}Base64urlIdentifies signing algorithm and token type
Payload{"sub": "user:42", "roles": ["analyst"], "exp": 1706745600}Base64urlCarries claims about the subject
SignatureHMAC-SHA256(base64url(header) + "." + base64url(payload), secret)Base64urlVerifies integrity and authenticity

The header and payload are not encrypted -- they are merely encoded. Anyone who possesses the token can decode and read the claims. The signature ensures that the claims have not been tampered with, but it does not hide them. For this reason, JWTs should never contain sensitive data (passwords, API keys) unless JWE encryption is applied.

Registered Claims

RFC 7519 defines a set of registered claim names with standardized semantics. While all claims are optional, certain claims are essential for secure JWT deployment:

ClaimNameTypeDescriptionPrismatic Usage
issIssuerStringEntity that issued the token"prismatic-platform"
subSubjectStringEntity the token describes"user:42" or "agent:archer"
audAudienceString/ArrayIntended recipient(s)"prismatic-api"
expExpirationNumericDateToken expiration timestampUnix timestamp, typically +1 hour
nbfNot BeforeNumericDateToken not valid before this timeUsed for scheduled token activation
iatIssued AtNumericDateToken creation timestampAutomatic, used for age calculations
jtiJWT IDStringUnique token identifierUUID, used for revocation list

Beyond registered claims, Prismatic extends the payload with private claims for RBAC enforcement:

{
  "iss": "prismatic-platform",
  "sub": "user:42",
  "aud": "prismatic-api",
  "exp": 1706745600,
  "iat": 1706742000,
  "jti": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "roles": ["analyst", "viewer"],
  "permissions": ["assets:read:all", "reports:read:team"],
  "team": "blue-team",
  "provider": "github"
}

Signing Algorithms

The choice of signing algorithm determines the security properties, key management requirements, and performance characteristics of JWT verification:

AlgorithmTypeKeyPerformanceUse Case
HS256Symmetric (HMAC)Shared secretFastSingle-service, internal APIs
HS384Symmetric (HMAC)Shared secretFastHigher security HMAC
HS512Symmetric (HMAC)Shared secretFastMaximum HMAC security
RS256Asymmetric (RSA)Public/private key pairSlowerMulti-service, federated identity
RS512Asymmetric (RSA)Public/private key pairSlowerHigh-security RSA
ES256Asymmetric (ECDSA)Public/private key pairMediumModern alternative to RSA, smaller keys
EdDSAAsymmetric (EdDSA)Public/private key pairFastState-of-the-art, Ed25519 curves

Symmetric algorithms (HS*) use a single shared secret for both signing and verification. They are fast but require distributing the secret to every service that needs to verify tokens -- a security liability in distributed systems.

Asymmetric algorithms (RS*, ES*, EdDSA) use a private key for signing and a public key for verification. Only the authentication service holds the private key; all other services verify tokens with the public key. This is the recommended approach for multi-service architectures.

# Guardian configuration with asymmetric signing
# config/config.exs
config :prismatic_web, PrismaticWeb.Guardian,
  issuer: "prismatic-platform",
  secret_key: {PrismaticWeb.Guardian, :fetch_secret_key, []},
  allowed_algos: ["RS256"],
  verify_issuer: true,
  ttl: {1, :hour}

defmodule PrismaticWeb.Guardian do
  use Guardian, otp_app: :prismatic_web

  def fetch_secret_key do
    # Load RSA private key from secure storage
    case Application.get_env(:prismatic_web, :jwt_private_key_path) do
      nil -> {:error, :missing_key_path}
      path -> JOSE.JWK.from_pem_file(path)
    end
  end

  def subject_for_token(%User{id: id}, _claims), do: {:ok, "user:#{id}"}
  def subject_for_token(%Agent{id: id}, _claims), do: {:ok, "agent:#{id}"}
  def subject_for_token(_, _), do: {:error, :invalid_subject}

  def resource_from_claims(%{"sub" => "user:" <> id}), do: {:ok, Accounts.get_user!(id)}
  def resource_from_claims(%{"sub" => "agent:" <> id}), do: {:ok, Agents.get_agent!(id)}
  def resource_from_claims(_), do: {:error, :invalid_claims}
end

Token Refresh Mechanism

Access tokens are intentionally short-lived to limit the window of exposure if compromised. The token refresh mechanism allows clients to obtain new access tokens without requiring the user to re-authenticate:

Token TypeLifetimeStorageRevocablePurpose
Access Token15 min - 1 hourClient memory (never localStorage)Via revocation listAPI authentication
Refresh Token1 day - 30 daysServer-side (encrypted)Direct database deletionAccess token renewal

The refresh flow works as follows:

  1. Client detects that the access token is expired or about to expire (checking exp claim)
  2. Client sends the refresh token to the token endpoint
  3. Server validates the refresh token, checks revocation status, and verifies the associated user still has active permissions
  4. Server issues a new access token (and optionally a new refresh token, implementing token rotation)
  5. Old refresh token is invalidated (rotation) to prevent replay attacks

Proactive refresh -- requesting a new token before expiration, typically 5 minutes before exp -- provides a seamless user experience without authentication interruptions.

Context in Prismatic

The Prismatic Platform uses JWT as the primary authentication mechanism for both human users and AI agents. The PrismaticWeb.Plugs.APIAuth plug validates JWTs on every incoming request, extracting claims for RBAC enforcement without database lookups. The API Gateway serves as the single validation point, ensuring consistent token verification across all backend services.

Token issuance occurs through two paths: direct authentication (username/password with MFA) produces a JWT with full user claims, while OAuth2 provider authentication (GitHub, Google, GitLab) produces a JWT after successful callback processing. Both paths result in identical JWT structures, making downstream authorization agnostic to the authentication method.

For agent-to-agent communication within the platform, JWTs carry agent tier claims (L1 through L5) alongside standard role claims. This enables the AIAD authority model to function as a JWT-verified permission hierarchy, where higher-tier agents can issue commands to lower-tier agents by presenting tokens with superior authority claims.

# JWT validation plug pipeline
defmodule PrismaticWeb.Router do
  use PrismaticWeb, :router

  pipeline :api_authenticated do
    plug PrismaticWeb.Plugs.APIAuth
    plug PrismaticWeb.Plugs.RequireRole, minimum: :viewer
    plug PrismaticWeb.Plugs.RateLimiter, by: :token_sub
  end

  scope "/api/v1", PrismaticWeb.API do
    pipe_through [:api, :api_authenticated]

    get "/assets", AssetController, :index
    get "/reports", ReportController, :index
    post "/scans", ScanController, :create
  end
end

Security Best Practices

PracticeDescriptionEnforcement
Always verify signatureNever trust payload without cryptographic verificationAutomatic in Guardian
Validate all claimsCheck exp, iss, aud, nbf on every requestGuardian configuration
Use asymmetric signingRS256/ES256 for multi-service architecturesPlatform default
Short access token TTL15-60 minute lifetimes limit compromise window1 hour default
Secure token storageHttpOnly cookies or memory-only; never localStorageServer-side sessions
Implement revocationMaintain a revocation list for emergency token invalidationETS-backed revocation cache
Rotate refresh tokensIssue new refresh token on each use, invalidate the old oneRotation on refresh
No sensitive data in payloadJWS payloads are readable by anyone with the tokenCode review enforcement
Use jti for uniquenessPrevents token replay across different contextsUUID generation on issuance
Transport over TLS onlyTokens in transit must be encrypted at the transport layerHTTPS enforced at gateway

Common Vulnerabilities

VulnerabilityAttackPrevention
Algorithm confusionAttacker changes alg to none or switches RSA to HMACExplicitly whitelist allowed algorithms
Key confusion (RS/HS)Using public RSA key as HMAC secretEnforce algorithm-key type matching
Token replayStolen token reused after user intentShort TTL + jti claim + revocation
Payload tamperingModified claims without re-signingAlways verify signature before reading claims
Cross-service token reuseToken for Service A used on Service BValidate aud claim per service
Expired token acceptanceClock skew allows expired tokensMaximum 30-second clock tolerance
  • OAuth2 - Authorization framework that issues JWTs as access tokens
  • RBAC - Role-based access control with roles carried in JWT claims
  • REST API - Stateless HTTP interface authenticated with JWTs
  • API Gateway - Entry point validating JWT tokens for all backend services
  • TLS - Transport encryption protecting JWT tokens in transit
  • Encryption at Rest - Protection of stored refresh tokens and signing keys
  • Plug - Elixir middleware executing JWT validation in request pipeline
  • Agent Tier - Agent authority claims embedded in JWTs
  • Rate Limiting - Per-token throttling using JWT sub claim
  • Observability - Monitoring JWT validation failures and token metrics

See Also

  • Architecture - Security architecture and token flow
  • Apps - Prismatic API and Web authentication
  • Technologies - Guardian, JOSE, and cryptographic libraries

Connect & Contribute

Created by Tomáš Korcak (korczis) | Open Source under GHL