We understand that making application security decisions is an important and daunting task. The security information can be overwhelming, especially when searching for information about OAuth 2.0 and OpenID Connect. We provide some general, recent information to help developers make decisions when architecting a solution.
In this post, we’ll provide an overview detailing the essential security and authentication flows, which flows to avoid in newer implementations, some security measures built into the specification, best practices for single-page application development, and some enhanced security features.
Many of these recommendations are based on the IETF Best Current Practices document, which you can refer to anytime. Regarding Duende IdentityServer, we’ve done our best to codify best practices when possible, but some decisions are still left to developers to implement.
Choosing the Correct Security Flow
In the context of OAuth, a flow is a step-by-step process in which an identity provider issues a token to a client. These flows can differ substantially based on the context. While OAuth 2.0 has many grant types and flows, and Duende IdentityServer supports them, we recommend you consider the two most popular options for an application built today: Code Flow and Client Credentials Flow.
The Code flow is essential when a user is involved in the authentication process. It allows users to grant access to an application to retrieve their information, and the identity provider returns a token for use on the user’s behalf. Information retrieved during the code flow could include emails, names, and other relevant details for the current application context. If you’ve ever logged in using a social media account, you’ve likely experienced this flow firsthand. With Duende IdentityServer, you can extend these flows while remaining true to the specification. In short, if user interaction with your application is involved, you likely want to start with the Code flow.
We recommend implementing Proof Key for Code Exchange (PKCE) for code flow because it ensures that front-channel and back-channel requests come from the same app instance. We also encourage you to keep all access token lifetimes short. Short lifetimes limit the window an attacker could use a token to access protected systems.
You use the Client Credentials flow when no end-users are involved. You may also hear this flow mentioned in contexts where “machine to machine” is used. Regarding configuration, developers identify a client by an ID and a secret credential to authenticate and provide access to protected data. For example, you have a service that executes a batch operation and needs to retrieve information from an existing API. You would register each client with the identity provider and share a secret between the client and the identity provider, which the clients then use to retrieve tokens to facilitate the interaction between each other.
Like Code Flow, we recommend shortening your Client Credentials flow token lifetimes to limit their effectiveness. Since these clients and their tokens typically have elevated access to sensitive systems because of their inherent trust level within your solutions, it’s best to err on the safe side.
OAuth 2.0 Flows to Avoid in 2025
There are two flows we actively recommend developers avoid or consider migrating away from: the Implicit Flow and the Resource Owner Password Flow.
The Implicit Flow has become a “bad practice” as it allows returning access tokens in an HTTP redirect without the receiving client confirming it has received the token.
In most cases, developers used this flow to authenticate in native or JavaScript clients. Today, the recommendation is to use the Code flow with PKCE enabled. If you’re still using Implicit Flow, we highly recommend migrating to the latest recommendation as soon as possible.
The Resource Owner Password flow is another bad practice, as it requires the user to send their identifier and password to the identity provider, leading to the handling of highly sensitive information. With many users still reusing passwords across services, this could lead to a higher-than-necessary risk for all parties involved and lead to attempts from malicious parties if passwords were compromised.
In both cases, if you’re still using any of these flows with IdentityServer4, we recommend upgrading to the latest version of Duende IdentityServer and adapting your applications to use the latest flows.
What is PKCE? And Why You Should Prefer It
PKCE stands for Proof Key for Code Exchange. It's an extension to the OAuth 2.0 authorization code flow that prevents attackers from abusing the authorization code grant. Without PKCE, an attacker who can steal authorization codes can inject those stolen codes into the flow, fool the client application into using them, and obtain a session with the access of another user.
This attack relies on the fact that the code flow uses two requests that are not bound or correlated. First, the client requests the code by redirecting to the authorize endpoint, and then subsequently it exchanges the code for tokens at the token endpoint. PKCE prevents this attack by binding the two requests together, using a secret that the client generates called the code verifier. When the client requests an authorization code from the authorization server, the client includes a one-way cryptographic hash of the code verifier, called the code challenge. The authorization server then stores the code challenge and issues the client an authorization code.
When the client redeems the authorization code for an access token, the client must also provide the code verifier. The authorization server then compares the hash of the code verifier to the stored code challenge. If the two values match, the authorization server issues the client an access token.
To recap, the initial authorization request includes a hash of a secret (the code challenge) and the subsequent token request includes the plaintext (the code verifier). The authorization server checks that the hash of the code verifier matches the code challenge, which provides assurances that the same party made the authorization and token requests.
The good news is that PKCE is already implemented by all production-worthy OAuth and OpenID Connect tools. PKCE is enabled by default for clients registered in Duende IdentityServer, and the ASP.NET OIDC authentication handler enables PKCE by default. Thus, PKCE is a simple and effective way to prevent attackers from abusing the authorization code grant.
Picking the Access Token Lifetime
You may want to read defined and precise numbers, but this is very much a “it depends” case. To pick the correct lifetime, you need to understand your business needs.
- What does a typical user’s session look like?
- How sensitive is the information?
- Is increased security at the cost of convenience warranted?
- Do I have the resources for increased token generation?
In practice, shorter token lifetimes are always more secure, as shorter windows limit a token's usefulness if it were to be compromised. That said, shorter token lifetimes may increase calls to the identity provider for new tokens to perform a user action. The increased issuing of tokens could add pressure to your solution’s resources and degrade the overall user experience.
We recommend starting with shorter lifetimes, measuring the impact on resources in a production environment, and talking with users about the overall application experience. Luckily, Duende IdentityServer implements OpenTelemetry, giving you a clear understanding of the impact of configuration changes on your identity provider.
Use Backend for Frontend for Single-Page Applications
It’s common practice for ASP.NET Core developers to use a single-page application framework to implement their applications' user interface (UI). Frameworks such as Angular, React, VueJs, and Blazor WASM power many modern ASP.NET Core solutions, and for a good reason: They’re powerful. In these circumstances, the client communicates with an API backend through HTTP requests, passing along the user’s intended actions and identifying information.
While SPAs can provide an enjoyable user experience, more companies are concluding that the threat of token exfiltration is too high when developers store tokens in Javascript-accessible locations. Leaked tokens can leave your systems accessible to nefarious parties and cataclysmic data leaks.
Backend for Frontend (BFF) is a pattern that pairs a SPA with a dedicated backend that stores tokens on the server side using encrypted/signed HTTP-only cookies for session management. This approach lowers the threat of token-based attacks and simplifies the solution's security practices.
Duende’s BFF offering also includes additional features that help accelerate SPA development, such as session management, automatic refresh token management, diagnostics, silent login, local and remote API integration, and more.
Adopting a “no tokens in the browser” policy will help you build more secure SPAs and keep up with the changing security practices implemented by browser vendors.
Enterprise-level Security with Resource Indicators and DPoP
Some organizations may want the additional security features offered in our Enterprise license, which include Resource Indicators and Demonstrating Proof of Possession (DPoP).
Resource Indicators allow clients to request access tokens for an individual resource server. This enhancement provides for API-specific features such as access token encryption and isolation of APIs outside the same trust boundary. It also prevents systems from accidentally creating tokens with too much access over time. Generally, a token with limited scope and actions provides more security, and developers should consider using this feature in high-security settings.
Demonstrating Proof of Possession, also known as DPoP, constrains tokens to the sender. Enabling DPoP requires the client to prove it has the right to use the token, typically using a private key. DPoP is a security measure that addresses token replay attacks by making it difficult for attackers to use stolen tokens since they would not have access to the private key. Enabling this feature can add additional layers of scrutiny to actions taken within your security boundaries.
Summary
While there is a lot of literature on OAuth 2.0, and Duende IdentityServer is a spec-compliant implementation of OpenID Connect, we want people to have a comfortable starting point when adopting the technology.
In short, consider Code Flow when handling user interactions and accessing resources on a user’s behalf. For machine-to-machine implementations, consider Client Credentials Flow, which is straightforward and provides the best security practice for solutions. When setting up either flow, consider balancing security, convenience, and resource utilization. With built-in OpenTelemetry, you can find the right balance when implementing Duende IdentityServer.
Speaking of balancing security and user experience, consider using a BFF with your SPA implementations to get the best of both worlds. Adopt a “no tokens in the browser” policy to prevent secrets from being leaked to the client. BFF can also make development more straightforward, help you accelerate development velocity, and increase your time to market.
For folks working in high-security environments such as finance, governments, or healthcare, consider our Enterprise edition, which provides access to Resource Indicators and DPoP features.
We hope this post gives folks a good starting point, and if you have any questions, please feel free to reach out.