The kit ships with secure defaults wherever possible. Some things, by their nature, you must configure for your environment — there’s no “secure default” for a JWT signing key or an allowed-origins list. This page is the ten-item checklist; skip none of them.
1. Set JwtOptions:SigningKey from a secrets manager
The repo’s dev-only key lives in appsettings.Development.json; base and production config leave the key empty, and startup validation refuses to boot with an empty key, a key shorter than 32 characters, or the sample replace-with-… placeholder. So the failure mode isn’t “silently forgeable tokens” — it’s “won’t start until you set one”. Set a real one:
openssl rand -base64 32Inject via secrets manager / environment variable:
JWTOPTIONS__SIGNINGKEY=...Never check the production key into git, and never reuse the dev key. See data protection for the secrets-management patterns.
2. Configure password policy to your compliance needs
The kit defaults:
- 10-character minimum with digit + uppercase + lowercase required (
IdentityOptionsinIdentityModule) - 5-password history (prevent reuse)
- 90-day expiry with 14-day warning
Adjust for your industry. Healthcare (HIPAA) and finance (PCI-DSS) tend to require longer histories and shorter expiries. Consumer products can be looser (longer minimum + no expiry, with strong 2FA).
{ "PasswordPolicy": { "PasswordHistoryCount": 12, "PasswordExpiryDays": 60, "PasswordExpiryWarningDays": 7, "EnforcePasswordExpiry": true }}3. Allowlist CORS origins
CorsOptions:AllowAll = true (and the SetIsOriginAllowed(_ => true) policy it enables) is dev only. Production needs the explicit lists — and note that appsettings.Production.json ships AllowedOrigins empty, which means no CORS middleware mounts at all until you fill it in; your front-ends on other origins will be blocked by the browser. See CORS & security headers.
{ "CorsOptions": { "AllowAll": false, "AllowedOrigins": [ "https://app.example.com", "https://admin.example.com" ], "AllowedHeaders": [ "content-type", "authorization" ], "AllowedMethods": [ "GET", "POST", "PUT", "DELETE" ] }}4. Review the security headers
The kit emits CSP, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, Referrer-Policy: strict-origin-when-cross-origin, and HSTS (on HTTPS responses) by default — verify they survive your reverse proxy, and tune the knobs:
{ "SecurityHeadersOptions": { "Enabled": true, "ExcludedPaths": [ "/scalar", "/openapi" ], "AllowInlineStyles": true, "ScriptSources": [], // add your analytics/widget origins here "StyleSources": [] }}If your production posture disables the API docs UI (item 7), you can drop the ExcludedPaths entries too. Test with browser dev tools open — CSP violations log to the console.
5. Turn rate limiting on and tune the values
RateLimitingOptions:Enabled is false in the base config (so local dev never trips it) and true in appsettings.Production.json — verify your deployed config actually enables it. The auth policy defaults to 10 requests per minute per user-or-IP; the global chained limiters cover tenant (1000/min), user (200/min), and IP (300/min):
{ "RateLimitingOptions": { "Enabled": true, "Auth": { "PermitLimit": 10, "WindowSeconds": 60, "QueueLimit": 0 }, "Tenant": { "PermitLimit": 1000, "WindowSeconds": 60, "QueueLimit": 0 }, "User": { "PermitLimit": 200, "WindowSeconds": 60, "QueueLimit": 0 }, "Ip": { "PermitLimit": 300, "WindowSeconds": 60, "QueueLimit": 0 } }}Adjust Auth to your traffic profile: relax for high-volume consumer apps on shared NAT, tighten for internal admin surfaces.
6. HTTPS everywhere
UseHttpsRedirection is on by default. Verify:
- Reverse proxy / load balancer terminates TLS with a valid certificate.
X-Forwarded-Proto: httpsforwards to the kit so the redirect middleware doesn’t double-redirect — and so the HSTS header (emitted only on HTTPS requests) actually fires.- HTTP/2 or HTTP/3 enabled at the LB for performance.
If you’re behind Cloudflare / a CDN, also enable “Always Use HTTPS” + “HSTS” at the CDN.
7. Lock down or remove debug endpoints
In production, decide:
/scalar(the OpenAPI browser) and/openapi/v1.json—appsettings.Production.jsonships withOpenApiOptions:Enabled = false, which removes both. If you re-enable them, consider whether revealing your API surface to the world is what you want. Note these paths also bypass the security-headers middleware and authorization (so the docs UI works) — another reason to keep them off in production./health— expose to your platform’s probes; firewall externally.- OpenTelemetry collector endpoints — these are inbound to your collector, not the kit, but verify they’re not Internet-exposed.
8. Lock down the Hangfire dashboard
The jobs dashboard (default route /jobs, configurable via HangfireOptions:Route) is gated by basic auth. Startup validation enforces a username of 3+ chars and a password of 12+ chars — empty values won’t boot. Verify:
- Username is not
admin(rename to something less guessable). - Password is long (16+ random chars) and not the dev value from
appsettings.Development.json. - Behind HTTPS (basic auth is plaintext otherwise).
- IP-allowlist at the reverse proxy (Cloudflare WAF rule, Nginx
allow/deny, etc.) — only your ops IPs.
{ "HangfireOptions": { "UserName": "ops-team", "Password": "set-via-secrets-manager", "Route": "/jobs" }}9. Configure Data Protection key persistence
Set CachingOptions:Redis. Without it, each instance maintains its own Data Protection key ring locally, and email-confirmation links, password reset links, and webhook signing secrets stop working across rolling deploys.
See data protection.
10. Enable audit retention
The kit’s AuditRetentionJob is opt-in (Enabled defaults to false). Without it, the audit table grows forever. Configure:
{ "Auditing": { "Retention": { "Enabled": true, "ActivityRetentionDays": 30, "EntityChangeRetentionDays": 90, "SecurityRetentionDays": 365, "ExceptionRetentionDays": 180, "DeleteBatchSize": 5000, "Cron": "30 3 * * *" } }}Adjust retention windows per your compliance regime. Most teams want security events kept much longer than activity events (a year vs a month is typical).
Bonus: items worth doing within the first month
These aren’t blockers, but the sooner the better:
- Make 2FA mandatory for admin roles. A leaked admin password compromises everything; require TOTP.
- Add monitoring + alerts on auth-failure spikes, impersonation events, and 5xx error rates.
- Run a vulnerability scan of the deployed image (Snyk, Trivy, Dependabot) to catch transitive CVEs early.
- Set up backup + restore tests for Postgres + MinIO. Backups you haven’t tested restoring are not backups.
- Document an incident-response runbook — who’s on-call, where the alerts go, how to revoke every session (admin revoke-all is built in), how to rotate the JWT signing key in an emergency.
Related
- Authentication, Authorization, Impersonation, Two-factor — the kit’s auth primitives.
- Webhook signing — HMAC integrity.
- Data protection — key persistence.
- CORS & headers — the network-layer hardening.