Security & Production Checklist
iam.dev-mode: true exists so a first boot needs zero secrets. In production it must be
false (or unset), and IAM’s secret validator refuses to start with missing or default
secrets — a deliberate fail-closed stance.
The checklist
Section titled “The checklist”iam: dev-mode: false # or remove the line entirely
jwt: algorithm: ES256 # DEFAULT — asymmetric; requires an EC P-256 keypair private-key: ${IAM_JWT_PRIVATE_KEY} # PEM PKCS#8 EC P-256, from your secret manager public-key: ${IAM_JWT_PUBLIC_KEY} # PEM X.509/SPKI — also published at /.well-known/jwks.json access-token-ttl: 15m refresh-token-ttl: 7d refresh-token-delivery: COOKIE # COOKIE | BODY | BOTH
default-tenant: admin-password: ${IAM_ADMIN_PASSWORD} # never the default
oauth2: client-secret-encryption-key: ${IAM_OAUTH2_KEY} # exactly 32 bytes callback-base-url: https://api.yourapp.com frontend-redirect-url: https://app.yourapp.com/auth/callback
security: bcrypt-strength: 12 # OWASP minimum 10 cors-allowed-origins: - https://app.yourapp.com # no localhost in prod
rate-limit: enabled: true # per-endpoint rules on auth routes- JWT signing configured for the chosen algorithm — ES256 (the default) needs a keypair, not
iam.jwt.secret(see below) - Secrets injected from a secret manager — never committed
-
dev-modeoff (ephemeral secrets are logged and don’t survive restarts) - HTTPS everywhere; refresh-token cookie is
Strictsame-site by default - CORS narrowed to real origins
- Rate limiting left on (login 10/min, forgot-password 3/5min, sensible defaults)
- Audit retention fits your compliance window (
iam.audit.retention-days, default 90) - If multi-instance: consider
iam.cache.store: redisfor instant cross-node invalidation
JWT signing (ES256 by default)
Section titled “JWT signing (ES256 by default)”The signing algorithm is iam.jwt.algorithm, and its default is ES256 (asymmetric,
EC P-256). This is the single most common production boot failure: a config carried over from an
older build that sets only iam.jwt.secret will not start — the secret validator runs at
bean-init (before the port binds) and refuses to boot without the ES256 keypair. Pick one:
ES256 — the default (recommended). Sibling services verify tokens against the public key via the JWKS endpoint, so no service but IAM ever holds signing material:
iam: jwt: algorithm: ES256 # optional — this is the default private-key: ${IAM_JWT_PRIVATE_KEY} # PEM PKCS#8, EC P-256 — REQUIRED, or boot fails public-key: ${IAM_JWT_PUBLIC_KEY} # PEM X.509/SPKI key-id: "" # optional; blank derives the RFC 7638 JWK thumbprint verification-keys: {} # optional rotation window: kid -> public key PEMGenerate a pair:
openssl ecparam -genkey -name prime256v1 -noout \ | openssl pkcs8 -topk8 -nocrypt -out jwt-private.pemopenssl ec -in jwt-private.pem -pubout -out jwt-public.pemWith an asymmetric algorithm active, IAM publishes GET /.well-known/jwks.json (RFC 7517 JWK
Set, permitAll; the endpoint does not exist in HS512 mode). Tokens carry a kid header;
verification-keys keeps retired public keys verifiable during a rotation window.
HS512 — symmetric opt-out (single-service / legacy). One shared secret, no JWKS endpoint:
iam: jwt: algorithm: HS512 secret: ${IAM_JWT_SECRET} # minimum 64 bytes (512 bits, RFC 7518 §3.2)The floor is a real 64 bytes / 512 bits — a 32–63-byte secret is rejected, as is a degenerate
one (fewer than 8 distinct bytes, e.g. "a".repeat(64)). Generate one with openssl rand -base64 48.
iam.dev-mode: true generates an ephemeral EC keypair per boot (or an HS secret only if you pin
algorithm: HS512) — never for production. See the
1.3.1 → 2.0.0 upgrade guide §4.1
for the full migration.
What you get out of the box
Section titled “What you get out of the box”- JWT access + refresh tokens, refresh rotation, token cleanup job
- Password hashing with BCrypt (cost 12)
- 2FA (TOTP + backup codes) —
iam.two-factor.enabled: true - OAuth2/OIDC login (Google, Microsoft, any OIDC issuer) with PKCE state handling and encrypted client secrets at rest
- Password reset with single-use, expiring tokens and optional session invalidation
- Audit trail of permission and entity changes (actor, IP, before/after JSON)
- Tenant write-guard + read filter in multi-tenant row-level mode
Bring your own security chain
Section titled “Bring your own security chain”IAM’s SecurityFilterChain is @ConditionalOnMissingBean — define your own and IAM backs
off, exposing its filters for you to compose. Extra public routes without replacing the chain:
iam: security: additional-permit-all: - /actuator/health - /public/**Honest notes
Section titled “Honest notes”- OAuth2 authorization state is in-memory — fine for single-instance; front multi-instance deployments with sticky sessions or an external store.
- IAM is an authorization-first library with OAuth2 login; it is not an IdP. For enterprise SSO federation (SAML, SCIM), put Keycloak/Okta in front for authentication and keep IAM for the fine-grained authorization they can’t do.
- Run your own security review before internet exposure. The library ships tested controls, not a substitute for your threat model.