The pitfalls of configuring OpenIddict from scratch
What can go wrong — and why it matters more than you think
In the previous article, we established what OpenIddict is and why it has become the go-to choice for .NET developers who need a standards-compliant authorization server. The framework is solid, well-maintained, and flexible.
That flexibility, however, comes at a cost. OpenIddict does not make decisions for you — it gives you the tools and expects you to use them correctly. And in the security domain, incorrect configuration is not just a bug: it is a vulnerability.
This article covers the most common mistakes developers make when setting up OpenIddict from scratch, and what the actual consequences are when those mistakes reach production.
Using development certificates in production
OpenIddict makes it very easy to get started locally. Two method calls and you have working signing and encryption certificates:
options.AddDevelopmentEncryptionCertificate()
.AddDevelopmentSigningCertificate();
These methods generate ephemeral certificates that live only in memory. Every time the application restarts, new certificates are generated — which means every token signed before the restart becomes invalid immediately. Users get logged out, API calls start failing, and refresh tokens stop working.
More critically, development certificates are not suitable for production from a security standpoint. They offer no guarantee of key strength, no rotation policy, and no audit trail. They exist purely to reduce friction during development.
⚠️ Real-world consequence
Development certificates in production means every application restart invalidates all active sessions. In a load-balanced environment with multiple instances, tokens signed by one instance cannot be validated by another. The result is constant, unpredictable authentication failures.
The correct approach is to provide persistent certificates — loaded from the filesystem or the Windows certificate store — that survive restarts and are shared across all instances of your application. This requires understanding certificate formats, key storage, and rotation strategies before you write a single line of configuration.
Token lifetimes that are too long — or not set at all
OpenIddict allows you to configure the lifetime of every token type: access tokens, refresh tokens, authorization codes, identity tokens. If you do not set these explicitly, the defaults apply — and while the defaults are reasonable, they may not be appropriate for your threat model.
The problem developers run into most often is access tokens with lifetimes that are far too long. An access token that is valid for 24 hours gives an attacker a 24-hour window to use a stolen token without any way to invalidate it, unless you have also set up token introspection or reference tokens.
// Setting explicit lifetimes is not optional in production
options.SetAccessTokenLifetime(TimeSpan.FromMinutes(15))
.SetRefreshTokenLifetime(TimeSpan.FromDays(14))
.SetAuthorizationCodeLifetime(TimeSpan.FromMinutes(5));
Short access token lifetimes force clients to refresh frequently, which gives you more opportunities to detect and revoke compromised sessions. Authorization codes should be especially short-lived — they are single-use credentials that should expire in seconds, not minutes.
💡 The balance to find
Very short lifetimes improve security but increase the frequency of token refresh operations, which adds load to your server and latency for your users. There is no universal answer — the right values depend on your application's sensitivity and usage patterns.
Not enforcing PKCE for public clients
PKCE (Proof Key for Code Exchange) is a mechanism that prevents authorization code interception attacks. It is mandatory for public clients — browser-based applications and mobile apps that cannot securely store a client secret.
OpenIddict supports PKCE, but it does not enforce it by default for every client. If you register a public client without requiring PKCE, that client can complete an authorization code flow without it. An attacker who intercepts the authorization code — through a malicious redirect, a browser extension, or a misconfigured redirect URI — can exchange it for tokens directly.
// This enables PKCE support but does not enforce it
options.AllowAuthorizationCodeFlow();
// This makes PKCE mandatory for all authorization code requests
options.AllowAuthorizationCodeFlow()
.RequireProofKeyForCodeExchange();
The difference between these two lines is small in code but significant in practice. Per-client enforcement requires additional configuration at the client registration level, which adds another surface for misconfiguration if not handled carefully.
Loose redirect URI validation
Redirect URIs are one of the most critical security boundaries in OAuth 2.0. After a user authenticates, the authorization server sends the authorization code to the redirect URI registered for that client. If an attacker can influence where that code is sent, they can capture it.
OpenIddict performs redirect URI validation, but the quality of that validation depends entirely
on how you register your clients. Common mistakes include registering URIs that are too broad,
allowing trailing wildcards, or accepting localhost URIs in production configurations.
⚠️ What a misconfigured redirect URI looks like
Registering https://app.example.com/ as a redirect URI and then accepting any
path under that origin is an open redirect vulnerability. An attacker crafts a link that redirects
the authorization code to a path they control under your domain. The fix is always
exact URI matching — no wildcards, no pattern matching, no exceptions.
Scope and audience misconfiguration
Scopes and audiences define what a token is valid for. A token issued with the wrong audience will be accepted by resource servers it was never intended to reach. A token with overly broad scopes gives the bearer more access than they should have.
Getting this right requires understanding the relationship between client registrations, scope definitions, and resource server validation — three separate concepts that need to be configured consistently. A mismatch at any point in the chain produces tokens that either do not work at all or work in ways you did not intend.
The openiddict audience search query that lands people on this site is a good indicator
of how often developers struggle with this specific area. It is one of the less intuitive parts
of OpenIddict's configuration, and the error messages when something is wrong are not always helpful.
Skipping token revocation
By default, JWT access tokens are self-contained and stateless. Once issued, they are valid until they expire — there is no built-in mechanism to invalidate them early. This is fine for short-lived tokens, but becomes a problem when you need to respond to a security incident, a user logs out, or an account is compromised.
OpenIddict supports reference tokens, which store the actual token payload in the database and issue only an opaque handle to the client. This makes revocation straightforward — you delete the record and the token stops working immediately. But reference tokens require token introspection at every API call, which adds a database lookup to each request.
Choosing between JWT and reference tokens — and setting up revocation correctly for either — is a decision that has to be made deliberately, based on your application's requirements. Skipping it because it seems complex is a common shortcut with real consequences.
The underlying pattern
Looking at these pitfalls together, a pattern emerges: none of them are caused by bugs in OpenIddict. They are all the result of decisions — or the absence of decisions — during configuration. OpenIddict is a general-purpose framework, and general-purpose frameworks cannot make security decisions on your behalf.
This is not a criticism of the library. It is simply the nature of building something flexible enough to serve a wide range of use cases. The trade-off is that every team using OpenIddict needs to develop enough expertise to configure it correctly — or rely on something that has already made those decisions in a secure and consistent way.
In the next article, we will look at exactly that: how IdentitySuite handles all of these configuration points out of the box, and what you get without writing a single line of security-critical configuration code.
About IdentitySuite
IdentitySuite simplifies enterprise authentication for .NET developers. Built on proven technologies like ASP.NET Core Identity and Openiddict, we eliminate the complexity of OAuth 2.0 and OpenID Connect implementation while maintaining enterprise-grade security standards.