OAuth 2.0 vs OpenID Connect: What Is the Difference?
OAuth 2.0 handles authorization: what can this application do? OpenID Connect (OIDC) handles authentication: who is this user? Most real applications need both, and the two protocols work together by design. OIDC is a layer on top of OAuth 2.0, not a replacement for it.
This post explains what each protocol does, how they differ, and how to configure them together in ASP.NET Core with Duende IdentityServer.
Updated: August 2026
What Is OAuth 2.0?
OAuth 2.0 is an authorization framework that lets an application request limited access to a user’s resources without handling the user’s password. The application gets an access token from an authorization server, then presents that token to an API. The API checks the token’s scopes and decides what the application is allowed to do.
OAuth 2.0 defines several grant types (authorization code, client credentials, refresh token) and the concept of scopes as permissions. It does not define a standard way to identify the user. An access token tells the API “this application has permission to read this data.” It does not say “this request came from Alice.”
For a deeper look at how OAuth 2.0 works, read What Is OAuth?. The full protocol is defined in RFC 6749.
What Is OpenID Connect?
OpenID Connect is an authentication protocol built on top of OAuth 2.0. It adds a standardized way to verify who the user is and retrieve basic profile information.
The key additions OIDC brings:
- ID tokens: A JWT (pronounced “jot”) that contains claims about the user, such as
sub(subject identifier),name, andemail. The client application validates this token to confirm the user’s identity. - UserInfo endpoint: An API that returns additional claims about the authenticated user.
- Discovery document: A JSON document at
/.well-known/openid-configurationthat publishes all the server’s endpoints, supported scopes, and signing keys. Clients use this instead of hardcoding URLs. - Standard scopes:
openid(required, returns thesubclaim),profile(name, family name, etc.),email,address, andphone.
Where OAuth 2.0 says “this app can access this resource,” OIDC says “this user is Alice, she authenticated 30 seconds ago with a password and a second factor, and here is her email.”
The full specification is OpenID Connect Core 1.0.
What Is the Difference Between OAuth 2.0 and OpenID Connect?
| Aspect | OAuth 2.0 | OpenID Connect |
|---|---|---|
| Purpose | Authorization (what can this app do?) | Authentication (who is this user?) |
| Token types | Access token, refresh token | ID token (plus access and refresh tokens from OAuth) |
| User identity | No standard mechanism | ID token with |
| Specification | ||
| Scopes | Application-defined (e.g., | Standardized ( |
| Discovery | Not defined | |
| Use alone? | Yes, for machine-to-machine API access | No, OIDC requires OAuth 2.0 underneath |
Think of it this way: OAuth 2.0 is the hotel key card. It grants access to specific rooms. OIDC is the check-in desk. It verifies who you are, then gives you the key card.
When Do You Need OAuth 2.0?
Use OAuth 2.0 alone when no user is involved and you just need one service to call another.
Machine-to-machine communication. A background service calls your API using the client credentials grant. The service authenticates with its own client ID and secret, gets an access token scoped to the API, and makes requests. No user identity is needed or expected.
Csharp
// Requesting a token with client credentials (using IdentityModel)
using IdentityModel.Client;
using var httpClient = new HttpClient();
var disco = await httpClient.GetDiscoveryDocumentAsync("https://identity.example.com");
var tokenResponse = await httpClient.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = disco.TokenEndpoint,
ClientId = "service.worker",
ClientSecret = "secret",
Scope = "api1"
});
// Use the access token to call the API
httpClient.SetBearerToken(tokenResponse.AccessToken!);
var result = await httpClient.GetAsync("https://api.example.com/reports");Other examples: nightly batch jobs pushing data to an API, microservices communicating within a cluster, CI/CD pipelines calling deployment APIs.
When Do You Need OpenID Connect?
Use OIDC when your application needs to know who the user is.
User sign-in. A web application redirects the user to the identity provider, the user authenticates, and the application receives an ID token with the user’s claims. The application now knows the user’s identity and can create a session.
Single sign-on (SSO). Multiple applications share the same identity provider. A user signs in once and is recognized across all applications without entering credentials again.
Profile information. Your application needs the user’s name, email, or other attributes. OIDC standard scopes (profile, email) provide these through the ID token or UserInfo endpoint.
When Do You Need Both?
Most web applications need both protocols. The user signs in (OIDC), and the application calls APIs on the user’s behalf (OAuth 2.0).
Here is what that flow looks like:
sequenceDiagram
participant User
participant WebApp
participant IdentityServer
participant API
User->>WebApp: Click "Sign In"
WebApp->>IdentityServer: Redirect to /authorize (scope: openid profile api1)
IdentityServer->>User: Show login page
User->>IdentityServer: Enter credentials
IdentityServer->>WebApp: Authorization code
WebApp->>IdentityServer: Exchange code for tokens
IdentityServer->>WebApp: ID token + access token
WebApp->>WebApp: Validate ID token, create session
WebApp->>API: GET /data (Authorization: Bearer {access_token})
API->>API: Validate access token, check scopes
API->>WebApp: 200 OK with dataThe OIDC flow authenticates the user and returns an ID token (identity) and an access token (authorization). The web app uses the ID token to establish a session, then uses the access token to call the API. One protocol flow, two tokens, two purposes.
How Do OAuth 2.0 and OIDC Work Together in ASP.NET Core?
On the client side, the ASP.NET Core OIDC middleware handles the entire authorization code flow with PKCE (pronounced “pixy”). It redirects to the identity provider, receives the authorization code, exchanges it for tokens, validates the ID token, and creates the user’s authentication session.
Client Configuration
Csharp
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
var builder = WebApplication.CreateBuilder(args);
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://identity.example.com";
options.ClientId = "web.app";
options.ClientSecret = "secret";
options.ResponseType = "code";
options.Scope.Clear();
options.Scope.Add("openid"); // OIDC: request user identity
options.Scope.Add("profile"); // OIDC: request profile claims
options.Scope.Add("api1"); // OAuth: request API access
options.SaveTokens = true;
options.MapInboundClaims = false;
});
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization();
// ... The openid and profile scopes trigger the OIDC behavior: the server returns an ID token with the user’s claims. The api1 scope triggers the OAuth 2.0 behavior: the server returns an access token scoped to the API. The ResponseType = "code" tells the middleware to use the authorization code flow with PKCE.
Server Configuration (Duende IdentityServer)
On the server side, Duende IdentityServer acts as both the OAuth 2.0 authorization server and the OpenID Connect provider. A single client registration handles both protocols:
Csharp
using Duende.IdentityServer.Models;
new Client
{
ClientId = "web.app",
ClientName = "Web Application",
ClientSecrets = { new Secret("secret".Sha256()) },
AllowedGrantTypes = GrantTypes.Code,
RequirePkce = true,
RedirectUris = { "https://webapp.example.com/signin-oidc" },
PostLogoutRedirectUris = { "https://webapp.example.com/signout-callback-oidc" },
// Scopes this client can request
AllowedScopes =
{
"openid", // OIDC: enables ID token with sub claim
"profile", // OIDC: adds name, family_name, etc. to ID token
"api1" // OAuth: enables access token for the API
},
AllowOfflineAccess = true // Enables refresh tokens
}; The openid and profile entries in AllowedScopes make this client an OIDC relying party. The api1 entry makes it an OAuth 2.0 client. IdentityServer issues the right tokens based on what the client requests.
What Does the Discovery Document Contain?
Every OpenID Connect provider publishes a discovery document at /.well-known/openid-configuration. This JSON document tells clients everything they need to interact with the server: which endpoints exist, what scopes and claims are supported, and where to find the signing keys.
Json
{
"issuer": "https://identity.example.com",
"authorization_endpoint": "https://identity.example.com/connect/authorize",
"token_endpoint": "https://identity.example.com/connect/token",
"userinfo_endpoint": "https://identity.example.com/connect/userinfo",
"end_session_endpoint": "https://identity.example.com/connect/endsession",
"jwks_uri": "https://identity.example.com/.well-known/openid-configuration/jwks",
"scopes_supported": ["openid", "profile", "email", "api1", "offline_access"],
"response_types_supported": ["code"],
"grant_types_supported": ["authorization_code", "client_credentials", "refresh_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"]
}Key fields:
- issuer: The identifier for this server. Tokens include this value, and clients validate it.
- authorization_endpoint: Where the client redirects the user to start the OIDC/OAuth flow.
- token_endpoint: Where the client exchanges an authorization code for tokens (backchannel call).
- userinfo_endpoint: Where the client retrieves additional user claims after authentication.
- jwks_uri: The public keys used to sign tokens. Clients and APIs fetch these to verify token signatures.
Clients should resolve all endpoint URLs from the discovery document instead of hardcoding them. The ASP.NET Core OIDC middleware does this when you set options.Authority. Duende IdentityServer publishes the discovery document at startup with no extra configuration.
The specification is OpenID Connect Discovery 1.0.
How Does Duende IdentityServer Implement Both Protocols?
Duende IdentityServer is both an OAuth 2.0 authorization server and an OpenID Connect provider in a single deployment. You register clients, define scopes and resources, and IdentityServer handles the protocol details: issuing ID tokens, access tokens, and refresh tokens according to the specs.
What IdentityServer supports:
- All recommended grant types: authorization code with PKCE, client credentials, refresh token exchange, device authorization, and token exchange (RFC 8693).
- Token types: ID tokens (always JWT), access tokens (JWT or reference), refresh tokens with configurable rotation.
- Discovery and JWKS: Published at startup, updated when signing keys rotate.
- Dynamic Client Registration: Clients can register programmatically (RFC 7591).
- Server-side sessions: Query, list, and revoke user sessions across clients.
- Security extensions: DPoP, mTLS, Pushed Authorization Requests (PAR), and FAPI 2.0 compliance.
IdentityServer integrates with ASP.NET Core’s authentication and authorization pipeline. On the server, you call AddIdentityServer() and configure clients, resources, and scopes. On the client, standard ASP.NET Core middleware (AddOpenIdConnect, AddJwtBearer) works without any Duende-specific client libraries.
For a complete overview, see the Duende IdentityServer documentation.
Get Started
- Quickstarts: Walk through protecting an API, adding user login, and building a JavaScript client step by step. Duende IdentityServer Quickstarts
- Duende BFF Security Framework: Secure single-page applications with a backend-for-frontend that handles tokens server-side. BFF Documentation
- Duende.AccessTokenManagement: Manage token lifecycle (caching, refresh, rotation) in ASP.NET Core with two lines of configuration. AccessTokenManagement Documentation
- Samples: Working code for common scenarios. Duende Samples on GitHub