Identity Security Hardening
When to Use This Skill
Use this skill when:
- Hardening a Duende IdentityServer deployment before promoting to production
- Configuring HTTPS, HSTS, and TLS requirements for the identity server host
- Evaluating or enforcing client secret policies (shared secrets vs. certificates vs.
private_key_jwt) - Setting PKCE requirements, restricting grant types, or locking down redirect URI validation
- Configuring Content Security Policy (CSP) and CORS for IdentityServer UI pages and endpoints
- Applying rate limiting to the token endpoint to protect against brute-force and enumeration attacks
- Tuning token lifetimes, enabling reference tokens, or implementing token replay detection
- Rotating signing keys or choosing between RS256 and ES256 algorithms
- Hardening session lifetimes, idle timeouts, and back-channel logout behavior
- Auditing an existing IdentityServer setup against OAuth 2.0 Security Best Current Practice (RFC 9700)
Core Principles
- HTTPS Everywhere — IdentityServer must only be reachable over HTTPS in production. Any HTTP request should be permanently redirected. HSTS with
includeSubDomainsandpreloadis the minimum bar. - Reduce Token Blast Radius — Short access token lifetimes, reference tokens for sensitive APIs, and audience validation ensure that a stolen token can do minimal damage.
- PKCE is Non-Negotiable — Every authorization code flow client must use PKCE, regardless of whether it is a public or confidential client.
RequirePkce = trueis the default; never disable it. - Asymmetric Client Authentication — Prefer certificate-based or
private_key_jwtclient authentication over shared secrets. Secrets that are never transmitted cannot be stolen in transit. - Strict Redirect URI Matching — Wildcards in redirect URIs are a critical attack surface. Every production URI must be fully qualified and must match exactly.
- Restrict Grant Types Per Client — Every client should only allow the grant types it actually uses. Disabling implicit flow and unused grants is one of the highest-impact, lowest-effort hardening steps.
- Defense in Depth — Combine transport security, token constraints, rate limiting, CSP, and CORS into a layered defense. No single control is sufficient.
Related Skills
identityserver-configuration— Server-side configuration of clients, resources, and signing keys that these hardening patterns build uponoauth-oidc-protocols— Protocol-level context for PKCE, PAR, DPoP, and grant type trade-offsaspnetcore-authentication— Applying OIDC authentication hardening in client applicationsaspnetcore-authorization— Enforcing authorization policies that consume the hardened tokens produced here
Docs: https://docs.duendesoftware.com/general/security-best-practices/
Sub-Documents
Pattern 1: Transport Security — HTTPS, HSTS, and TLS
IdentityServer handles credentials and tokens. Every byte must travel over TLS. ASP.NET Core provides the pipeline middleware to enforce this.
Configure HSTS options in Program.cs before Build():
Behind a Reverse Proxy
When IdentityServer sits behind a load balancer or reverse proxy that terminates TLS, the inner request arrives as HTTP. Configure ForwardedHeaders so IdentityServer sees the correct scheme:
Important: Without
ForwardedHeaders, IdentityServer publishes anhttp://issuer URI in the discovery document, causing token validation failures in every downstream API.
Kestrel TLS Configuration
For direct Kestrel hosting (no reverse proxy), configure TLS explicitly:
Pattern 2: Signing Key Security — Algorithm Selection and Rotation
Signing keys are the root of trust for every token IdentityServer issues. The default RS256 algorithm is broadly compatible. ES256 (ECDSA) offers smaller tokens and is appropriate for new deployments. Supported signing algorithm families are RS (RSA PKCS#1), PS (RSA-PSS), and ES (ECDSA).
Rotation overlap rule: Always publish a new public key before using it to sign tokens, and keep a retired public key available until all tokens signed with it have expired. Consumers must be able to fetch the key (via JWKS) both before it starts signing and after it stops. Automatic Key Management handles this overlap automatically; manual key managers must do phased rotation (below).
Automatic Key Management (Recommended)
Key Storage — ASP.NET Data Protection
Automatic key management encrypts signing keys at rest using ASP.NET Data Protection. Configure Data Protection to use durable, shared storage. See ASP.NET Core Data Protection for complete configuration guidance.
v8 licensing note: On IdentityServer v8, Automatic Key Management, Server-Side Sessions, and SAML throw at startup when a license is present but lacks the entitlement (other licensed features only log a warning). Run lower environments with the production license key so entitlement gaps surface before production. Also note the v8 license key is a signed JWT with a
kidheader and fails on v7/BFF runtimes withIDX10503. See ASP.NET Core Data Protection for complete configuration guidance.
Warning: Never store Data Protection keys on ephemeral storage (e.g., container local disk). If keys are lost, all encrypted data (persisted grants, cookies, server-side sessions) becomes unreadable.
Manual Key Rotation (Three-Phase Process)
When using static keys, never swap them in a single deployment. Use a phased rotation to avoid breaking in-flight token validation:
Pattern 3: Token Constraints — Lifetimes, Reference Tokens, and Audience Validation
Token constraints limit the damage from token compromise and ensure tokens are only usable at their intended audience.
Token Lifetime Tuning
Reference Tokens
Use reference tokens when:
- Tokens contain sensitive claims that must not be visible to intermediaries
- Immediate revocation is required (JWTs remain valid until expiry)
- Token size is a concern (reference tokens are short opaque handles)
The API must call the introspection endpoint to validate reference tokens:
Audience Validation
Audience validation ensures an access token issued for one API cannot be replayed at a different API. Use ApiResource to set explicit aud claims:
Validate audience on each API:
Pattern 4: PKCE Enforcement
PKCE prevents authorization code interception attacks. RequirePkce = true is the default in Duende IdentityServer and must never be disabled for any interactive client.
For public clients (native apps, SPAs without BFF), PKCE is the only protection since they cannot hold a secret:
Pattern 5: Client Secret Management
Client authentication quality directly determines the strength of the authorization boundary. Upgrade from shared secrets to asymmetric credentials wherever possible.
Hierarchy of Client Authentication Strength
Shared Secret (Minimum Baseline — Avoid for Sensitive Clients)
Store secrets outside source control. Never hash secrets inline with literals:
Private Key JWT (Recommended)
The client holds a private key and signs a JWT assertion. IdentityServer validates the assertion using the client's registered public key. No secret is ever sent over the wire.
The client sends a signed JWT assertion at the token endpoint (using Duende.AccessTokenManagement or IdentityModel):
Secret Rotation
Never rotate secrets with a hard cut-over. Register the new secret alongside the old one, deploy clients, then remove the old secret:
Custom Secret Validation (ISecretValidator)
Implement ISecretValidator to enforce custom secret policies (e.g., key minimum length, algorithm restrictions):
Pattern 6: Redirect URI Validation
Authorization code injection via open redirectors is one of the most critical OAuth attack vectors. Redirect URI validation must be exact-match in production.
Strict Matching (Default Behavior)
Duende IdentityServer validates redirect URIs by exact string comparison. This is the correct behavior:
Custom Redirect URI Validator
For legitimate dynamic scenarios (e.g., multi-tenant apps with per-tenant domains), implement IRedirectUriValidator with explicit allow-listing from a trusted data source:
Register the custom validator:
Pattern 7: Grant Type Restrictions
Each enabled grant type expands the attack surface. Disable every grant type a client does not use.
Disable Implicit Flow Globally
Implicit flow is deprecated by RFC 9700. Ensure no client uses it:
Principle of Least Grant
Custom Grant Validation
For extension grants, always validate the grant assertion rigorously:
Pattern 8: CORS Configuration
Set AllowedCorsOrigins per client with exact scheme+host+port — no trailing slashes, no wildcards. For dynamic tenant scenarios, implement ICorsPolicyService with a custom repository. Never use AllowAnyOrigin for IdentityServer endpoints.
See docs/cors-csp.md [blocked] for the complete
ICorsPolicyServiceimplementation and CORS configuration examples.
Pattern 9: Content Security Policy
Add a middleware that appends Content-Security-Policy, X-Frame-Options: DENY, X-Content-Type-Options: nosniff, and Referrer-Policy headers to all IdentityServer UI paths (/account, /consent, /connect, /diagnostics). Use frame-ancestors 'none' and object-src 'none' as the minimum bar.
See docs/cors-csp.md [blocked] for the complete CSP middleware implementation with inline examples.
Pattern 10: Rate Limiting
Duende IdentityServer has no built-in rate limiting. Use ASP.NET Core AddRateLimiter (set RejectionStatusCode = 429), but note a critical constraint: IdentityServer matches its protocol endpoints (/connect/token, /connect/authorize) with its own middleware, not ASP.NET Core endpoint routing. You therefore cannot attach a named per-endpoint policy to those endpoints — only the global limiter applies. Approximate per-endpoint limits by partitioning the global limiter on context.Request.Path. Named policies (RequireRateLimiting) still work on your own routed Razor Pages (login/consent). In load-balanced deployments, use X-Forwarded-For (after ForwardedHeaders middleware) for accurate IP partitioning. For identity-aware limits, add an ICustomTokenRequestValidator (runs after the client/user are known).
See docs/rate-limiting.md [blocked] for the complete rate limiter configuration, the protocol-endpoint caveat, and the
ICustomTokenRequestValidatorapproach.
Pattern 11: Session Security
Enable server-side sessions via idsvrBuilder.AddServerSideSessions(). Set CookieSlidingExpiration = false and a fixed CookieLifetime (e.g., 8 hours). Configure ExpiredSessionsTriggerBackchannelLogout = true and CoordinateClientLifetimesWithUserSession = true. Set BackChannelLogoutUri on each client for server-to-server session termination notification.
See docs/session-hardening.md [blocked] for the complete session configuration and back-channel logout client setup.
Pattern 12: Input Validation and InputLengthRestrictions
IdentityServer validates all incoming request parameters against configurable length limits. Tighten these to reduce injection and memory exhaustion risks.
Pattern 13: Audit Logging via Events
Events emit high-level, structured audit records (login success/failure, token issuance, consent, errors) suitable for a security audit trail. Audit events can contain usernames, subject/client ids, scopes, redirect URIs, and IPs — token values are obfuscated.
Events are NOT enabled by default. Turn them on in AddIdentityServer:
IdentityServer raises protocol events itself, but UI actions (login success/failure) must be raised by your UI code. Inject IEventService and call RaiseAsync(...):
Custom Sink — Replaces the Default Sink
Implement IEventSink and register it. IEventService sends each event to exactly ONE IEventSink, so registering a custom sink REPLACES the default (ASP.NET Core logger) sink. If you still want log output, the custom sink must forward to logging itself.
Custom events derive from the base Event class with a unique event id.
Common Pitfalls
1. Disabling PKCE
2. Wildcard Redirect URIs
3. Implicit Flow Still Enabled
4. Accepting ForwardedHeaders From Any Source
5. Plaintext Secrets in Source Control
6. HTTP Issuer URI
7. Long-Lived Access Tokens
8. Missing Audience Validation at the API
9. Shared Keys Across Environments
Production Security Checklist
Resources
- Duende IdentityServer Deployment — Duende Docs
- Key Management — Duende Docs
- Client Authentication — Duende Docs
- CORS — Duende Docs
- Reference Tokens — Duende Docs
- Server-Side Sessions — Duende Docs
- Pushed Authorization Requests — Duende Docs
- IdentityServerOptions Reference — Duende Docs
- OAuth 2.0 Security Best Current Practice (RFC 9700)
- PKCE (RFC 7636)
- JWT Client Authentication (RFC 7523)
- mTLS Client Authentication (RFC 8705)
- OWASP OAuth 2.0 Security Cheat Sheet
- ASP.NET Core Data Protection — Microsoft Docs
- ASP.NET Core Rate Limiting — Microsoft Docs

