checkId: check.security.jwt.config plugin: stellaops.doctor.security severity: fail tags: [security, jwt, authentication]

JWT Configuration

What It Checks

Validates JWT token signing and validation configuration. The check only runs when a JWT configuration section exists (Jwt or Authentication:Jwt). It inspects the following settings:

SettingThreshold/ConditionSeverity
SigningKeyNot configuredfail
SigningKeyShorter than 32 charactersfail
IssuerNot configuredfail
AudienceNot configuredfail
ExpirationMinutesGreater than 1440 (24 hours)warn
Algorithmnonefail — completely insecure
AlgorithmHS256warn — acceptable but RS256/ES256 recommended

Default values if not explicitly set: ExpirationMinutes = 60, Algorithm = HS256.

Evidence collected includes: whether a signing key is configured, key length, issuer, audience, expiration minutes, and algorithm.

Why It Matters

JWT tokens are the primary authentication mechanism for API access. A missing or short signing key allows token forgery. The none algorithm disables signature verification entirely. Missing issuer or audience values disable critical validation claims, allowing tokens from other systems to be accepted. Long expiration times increase the window of opportunity if a token is compromised.

Common Causes

How to Fix

Docker Compose

Set JWT configuration as environment variables:

environment:
  Jwt__SigningKey: "<generate-a-strong-key-at-least-32-chars>"
  Jwt__Issuer: "https://stella-ops.local"
  Jwt__Audience: "stellaops-api"
  Jwt__ExpirationMinutes: "60"
  Jwt__Algorithm: "RS256"

Generate a strong signing key:

openssl rand -base64 48

Bare Metal / systemd

Edit appsettings.json:

{
  "Jwt": {
    "SigningKey": "<strong-key>",
    "Issuer": "https://stella-ops.yourdomain.com",
    "Audience": "stellaops-api",
    "ExpirationMinutes": 60,
    "Algorithm": "RS256"
  }
}

For RS256, generate a key pair:

openssl genrsa -out jwt-private.pem 2048
openssl rsa -in jwt-private.pem -pubout -out jwt-public.pem

Kubernetes / Helm

Store the signing key as a Kubernetes Secret:

kubectl create secret generic stellaops-jwt \
  --from-literal=signing-key="$(openssl rand -base64 48)"

Reference in Helm values:

jwt:
  issuer: "https://stella-ops.yourdomain.com"
  audience: "stellaops-api"
  expirationMinutes: 60
  algorithm: "RS256"
  signingKeySecret: "stellaops-jwt"

Verification

stella doctor run --check check.security.jwt.config