Oauth Oidc Protocols

作者 DuendeSoftwarefb32edc51982無授權條款9 個星標收錄於 2026年10月8日更新於 2026年10月8日儲存庫4 週前更新

OAuth 2.0 and OpenID Connect protocol fundamentals including authorization code flow with PKCE, client credentials, refresh tokens, discovery documents, JWKS, and token introspection. Protocol-level troubleshooting and compliance.

AI 產生的概覽

說明 OAuth 2.0 與 OpenID Connect 協定基礎、流程、權杖、探索文件及疑難排解。

功能
此技能提供 OAuth 2.0 與 OpenID Connect 協定基礎的參考指引,涵蓋授權碼加 PKCE、用戶端認證及更新權杖交換等授權類型。它說明權杖結構、探索文件、JWKS、內省、撤銷、範圍,以及 PAR 和 DPoP 等安全擴充。它也包含協定偵錯檢查清單和權杖交換失敗的常見陷阱。
適用情境
適用於選擇 OAuth 2.0 授權類型、偵錯權杖交換或重新導向式驗證,或解讀身分權杖與存取權杖中的宣告和標頭。也適用於 PKCE、探索文件、JWKS、內省、更新權杖輪替和協定安全審查工作。
執行需求
無需指令碼或特殊工具,僅為純說明性參考資料。部分範例引用 IdentityModel 和 Duende IdentityServer 等 .NET 程式庫,這些範例隱含需要存取身分提供者的探索端點。

OAuth 2.0 & OpenID Connect Protocols

When to Use This Skill

Use this skill when:

  • Choosing the correct OAuth 2.0 grant type for a scenario
  • Debugging token exchange flows or redirect-based authentication
  • Understanding what claims and headers appear in identity tokens vs access tokens
  • Implementing or troubleshooting PKCE (Proof Key for Code Exchange)
  • Working with discovery documents, JWKS endpoints, or token introspection
  • Reviewing security properties of different protocol flows
  • Implementing refresh token rotation or token revocation

Core Principles

  1. OAuth 2.0 is for Authorization, OIDC is for Authentication — OAuth alone does not tell you who the user is. OpenID Connect adds an identity layer (the ID token) on top of OAuth.
  2. Authorization Code + PKCE is the Universal Flow — Use it for web apps, SPAs (via BFF), native apps, and any interactive scenario. It replaced implicit flow.
  3. Tokens are Opaque to Clients — Clients should not parse access tokens. Only the resource server (API) validates access tokens. Clients use the ID token for authentication.
  4. Discovery Documents are the Source of Truth — Always resolve endpoints from /.well-known/openid-configuration rather than hardcoding URLs.
  5. Refresh Tokens Require Secure Storage — Refresh tokens are long-lived credentials. Rotate them on every use (OneTimeOnly) and store them server-side.

Related Skills

  • identityserver-configuration — Server-side configuration of clients, resources, and scopes
  • aspnetcore-authentication — Implementing OIDC authentication in ASP.NET Core apps
  • token-management — Automated token lifecycle with Duende.AccessTokenManagement
  • identity-security-hardening — Security hardening including DPoP, PAR, and FAPI
  • duende-bff — Backend-for-Frontend pattern for SPAs

Docs: https://docs.duendesoftware.com/identityserver/fundamentals


Concept 1: The OAuth 2.0 / OIDC Mental Model

Roles

RoleOAuth 2.0 TermOIDC TermExample
UserResource OwnerEnd-UserA person logging in
Browser/AppClientRelying Party (RP)ASP.NET Core web app
Token ServerAuthorization ServerOpenID Provider (OP)Duende IdentityServer
APIResource Server—ASP.NET Core Web API

Tokens

TokenPurposeWho Consumes ItFormat
ID TokenProves user identityClient applicationAlways JWT
Access TokenAuthorizes API callsResource server (API)JWT or reference
Refresh TokenObtains new access tokensClient applicationOpaque handle

Critical Rule: Clients authenticate users with the ID token. Clients authorize API calls with the access token. Never use an access token to determine who a user is. Never send an ID token to an API.


Concept 2: Grant Types (Flows)

Authorization Code + PKCE (Recommended for All Interactive Scenarios)

The authorization code flow with PKCE is the recommended flow for all clients that involve a user. PKCE prevents authorization code interception attacks.

How it works:

  1. Client generates a random code_verifier and its SHA256 hash code_challenge
  2. Client redirects user to the authorize endpoint with code_challenge
  3. User authenticates at IdentityServer and consents (if required)
  4. IdentityServer redirects back with an authorization code
  5. Client exchanges the code + code_verifier at the token endpoint
  6. IdentityServer verifies the verifier matches the original challenge
  7. IdentityServer returns ID token + access token (+ refresh token if offline_access scope)
┌──────┐          ┌──────────┐          ┌──────────────┐│Client│          │  Browser  │          │IdentityServer│└──┬───┘          └────┬─────┘          └──────┬───────┘   │  1. Generate PKCE │                       │   │  code_verifier    │                       │   │  code_challenge   │                       │   │                   │                       │   │  2. Redirect ───────────────────────────► │   │     /authorize?code_challenge=...         │   │                   │  3. User logs in      │   │                   │  ◄──────────────────► │   │                   │                       │   │  4. Redirect back ◄────────────────────── │   │     ?code=abc123  │                       │   │                   │                       │   │  5. POST /token ─────────────────────────►│   │     code=abc123&code_verifier=...         │   │                   │                       │   │  6. Tokens ◄──────────────────────────────│   │     { id_token, access_token,             │   │       refresh_token }                     │   └───────────────────┴───────────────────────┘

When to use: Web applications, native apps, SPAs (via BFF pattern).

Client Credentials

For machine-to-machine communication with no user involvement.

┌──────────┐                    ┌──────────────┐│  Service  │                    │IdentityServer│└────┬─────┘                    └──────┬───────┘     │  POST /token                    │     │  grant_type=client_credentials  │     │  client_id=...                  │     │  client_secret=...              │     │  scope=api1                     │     │  ───────────────────────────►   │     │                                 │     │  { access_token }               │     │  ◄───────────────────────────   │     └─────────────────────────────────┘

When to use: Background services, daemons, server-to-server API calls.

Key difference: No user identity — the access token contains only client claims, not user claims.

Refresh Token Exchange

┌──────┐                         ┌──────────────┐│Client│                         │IdentityServer│└──┬───┘                         └──────┬───────┘   │  POST /token                       │   │  grant_type=refresh_token          │   │  refresh_token=old_rt              │   │  ─────────────────────────────►    │   │                                    │   │  { access_token,                   │   │    refresh_token: new_rt }         │   │  ◄─────────────────────────────    │   └────────────────────────────────────┘

With RefreshTokenUsage = OneTimeOnly, each refresh returns a new refresh token. The old one is invalidated. This enables refresh token rotation, a key security measure. Note: the default changed to ReUse in IdentityServer v7.0 — set OneTimeOnly explicitly for rotation.

Deprecated / Discouraged Flows

FlowStatusWhy
Implicit (token / id_token)DeprecatedTokens in URL fragments; no PKCE protection
Resource Owner Password (ROPC)DiscouragedClient handles credentials directly; no MFA support
HybridReplacedUse Authorization Code + PKCE instead

Concept 3: Discovery and JWKS

Discovery Document

Every OpenID Connect provider publishes a discovery document at /.well-known/openid-configuration. This JSON document advertises:

  • Endpoint URLs (authorize, token, userinfo, introspection, revocation, end_session)
  • Supported grant types, scopes, claims, and response types
  • Signing algorithms and JWKS URI
  • Token endpoint authentication methods
csharp
// ✅ Use IdentityModel to fetch discovery programmaticallyusing var httpClient = new HttpClient();var disco = await httpClient.GetDiscoveryDocumentAsync("https://identity.example.com");
if (disco.IsError) throw new Exception(disco.Error);
var tokenEndpoint = disco.TokenEndpoint;var jwksUri = disco.JwksUri;

Best Practice: Never hardcode endpoint URLs. Always resolve them from the discovery document. This ensures your application adapts to URL changes and load balancer configurations.

JWKS (JSON Web Key Set)

The JWKS endpoint (advertised via the jwks_uri field in the discovery document; in Duende IdentityServer this is /.well-known/openid-configuration/jwks) publishes the public keys used to verify token signatures. APIs and clients fetch this to validate JWTs.

Key rotation: When IdentityServer rotates signing keys, the new key appears in JWKS during the propagation period before it becomes the active signing key. Client libraries cache JWKS for 24 hours by default.


Concept 4: Token Anatomy

ID Token (JWT)

json
{  "iss": "https://identity.example.com",  "sub": "818727",  "aud": "web.app",  "exp": 1311281970,  "iat": 1311280970,  "nonce": "n-0S6_WzA2Mj",  "auth_time": 1311280969,  "at_hash": "77QmUPtjPfzWtF2AnpK9RQ",  "amr": ["pwd", "mfa"],  "name": "Alice Smith",  "email": "[email protected]"}

Key claims:

  • iss — Issuer (must match your IdentityServer URL)
  • sub — Subject (unique user identifier)
  • aud — Audience (must match the client ID)
  • nonce — Replay protection (sent in authorize request, echoed in token)
  • at_hash — Hash of the access token (binds the ID token to the access token)
  • amr — Authentication methods used

Access Token (JWT)

json
{  "iss": "https://identity.example.com",  "aud": "https://api.example.com",  "client_id": "web.app",  "sub": "818727",  "scope": "openid profile api1",  "exp": 1311284570,  "iat": 1311280970,  "jti": "unique-token-id"}

Key claims:

  • aud — The API resource(s) this token is valid for
  • client_id — Which client requested this token
  • scope — Granted permissions
  • jti — Unique token identifier (for revocation tracking)

Reference Tokens

Reference tokens are not self-contained JWTs. Instead, the access token is an opaque identifier. The API must call the introspection endpoint to validate it:

POST /connect/introspectContent-Type: application/x-www-form-urlencoded
token=<reference_token>&token_type_hint=access_token

When to use reference tokens:

  • Tokens contain sensitive claims you don't want exposed to intermediaries
  • You need immediate token revocation (JWT lifetimes are not revocable until expiry)
  • Token size is a concern (reference tokens are small opaque strings)

Concept 5: Token Introspection and Revocation

Introspection

APIs validate reference tokens by calling the introspection endpoint. The API authenticates itself with its own secret:

csharp
// Using IdentityModelvar introspectionResponse = await httpClient.IntrospectTokenAsync(    new TokenIntrospectionRequest    {        Address = disco.IntrospectionEndpoint,        ClientId = "api1",        ClientSecret = "api1-secret",        Token = accessToken    });
if (!introspectionResponse.IsActive){    // Token is invalid, expired, or revoked}

Revocation

Clients can revoke access tokens and refresh tokens:

csharp
var revocationResponse = await httpClient.RevokeTokenAsync(    new TokenRevocationRequest    {        Address = disco.RevocationEndpoint,        ClientId = "web.app",        ClientSecret = "secret",        Token = refreshToken,        TokenTypeHint = "refresh_token"    });

What can actually be revoked: revocation (RFC 7009) only deactivates reference access tokens and refresh tokens, which are persisted server-side. A JWT access token is stateless and cannot be revoked — it stays valid until exp. Use reference tokens when you need immediate invalidation.

Client Authentication: private_key_jwt

Confidential clients can authenticate with a signed JWT assertion instead of a shared secret. The assertion's aud MUST be the authorization server's issuer identifier (disco.Issuer), never the token endpoint URL — the latter enabled token-endpoint confusion attacks (CVE-2025-27370 / CVE-2025-27371). Set the JWT typ header to client-authentication+jwt to opt into strict audience validation (RFC 7523bis).


Concept 6: Scopes and Claims Mapping

Scopes Control What's in Tokens

Scope requestedWhat it controlsToken affected
openidReturns sub claimID token
profileReturns name, family_name, etc.ID token / userinfo
emailReturns email, email_verifiedID token / userinfo
api1Grants access to APIAccess token
offline_accessReturns refresh tokenRefresh token issued

Claims Destinations

By default, IdentityServer emits identity claims to the ID token and the userinfo endpoint. Claims associated with API scopes go into the access token. The IProfileService controls claim emission:

csharp
public class ProfileService : IProfileService{    public Task GetProfileDataAsync(ProfileDataRequestContext context)    {        // Add claims based on the requested resources        var claims = GetClaimsForUser(context.Subject);        context.IssuedClaims.AddRange(            claims.Where(c => context.RequestedClaimTypes.Contains(c.Type)));        return Task.CompletedTask;    }
    public Task IsActiveAsync(IsActiveContext context)    {        context.IsActive = true; // Check if user account is still active        return Task.CompletedTask;    }}

Concept 7: Security Extensions

Pushed Authorization Requests (PAR)

PAR moves the authorization parameters from the query string to a backchannel POST, preventing parameter tampering and URL length issues:

1. Client POSTs parameters to /connect/par → gets a request_uri2. Client redirects user to /authorize?request_uri=...&client_id=...

DPoP (Demonstrating Proof-of-Possession)

DPoP binds access tokens to a client's cryptographic key, preventing token theft and replay:

1. Client generates a key pair2. Client creates a DPoP proof (signed JWT with the public key)3. Client sends the DPoP proof in the DPoP header with the token request4. IdentityServer binds the token to the key via a "cnf" claim5. API verifies the DPoP proof matches the token's "cnf" claim

FAPI 2.0

Financial-grade API profile requires PAR, DPoP or mTLS, and stricter validation. Duende IdentityServer supports FAPI 2.0 compliance from v7.3+.


Common Pitfalls

1. Using Access Tokens for Authentication

csharp
// ❌ WRONG — Access tokens are for authorization, not authenticationvar userId = accessToken.Claims.First(c => c.Type == "sub").Value;// The access token's audience is the API, not your app
// ✅ CORRECT — Use the ID token (via the authentication middleware)var userId = User.FindFirst("sub")?.Value;

2. Parsing Access Tokens in the Client

csharp
// ❌ WRONG — Clients should treat access tokens as opaquevar handler = new JwtSecurityTokenHandler();var jwt = handler.ReadJwtToken(accessToken);// This breaks when the server switches to reference tokens
// ✅ CORRECT — Only the resource server (API) validates access tokens// The client just forwards the token in the Authorization headerhttpClient.SetBearerToken(accessToken);

3. Missing PKCE

csharp
// ❌ WRONG — No PKCE leaves authorization code flow vulnerable// Duende IdentityServer requires PKCE by default (RequirePkce = true)
// ✅ The ASP.NET Core OIDC handler sends PKCE automatically since .NET 7// No extra configuration needed on the client side

4. Ignoring Token Expiration

csharp
// ❌ WRONG — Using a token without checking expirationhttpClient.SetBearerToken(cachedAccessToken); // Might be expired
// ✅ CORRECT — Use Duende.AccessTokenManagement for automatic refresh// See the `token-management` skillbuilder.Services.AddOpenIdConnectAccessTokenManagement();

5. Hardcoding Endpoint URLs

csharp
// ❌ WRONG — Breaks when server URL changesvar tokenEndpoint = "https://identity.example.com/connect/token";
// ✅ CORRECT — Resolve from discoveryvar disco = await httpClient.GetDiscoveryDocumentAsync(authority);var tokenEndpoint = disco.TokenEndpoint;

Protocol Debugging Checklist

When a token exchange fails, check these in order:

  1. Discovery document — Is /.well-known/openid-configuration reachable? Does it return valid JSON?
  2. Client ID — Does the client ID in the request exactly match the server registration?
  3. Redirect URI — Exact string match including scheme, host, port, path, and trailing slash
  4. Scopes — Are all requested scopes registered in AllowedScopes on the client?
  5. Grant type — Is the grant type in the request allowed by the client's AllowedGrantTypes?
  6. PKCE — Is the client sending code_challenge and code_verifier? Duende IS requires PKCE by default.
  7. Client secret — Is the secret correct? Check for encoding issues (Sha256 hash, not plaintext).
  8. Clock skew — Is the server time within acceptable bounds for token validation? (default: 5 min)
  9. HTTPS — Is the authorize redirect using HTTPS? Mixed content blocks cause silent failures.
  10. CORS — If calling the token endpoint from a browser, is the origin in AllowedCorsOrigins?

Resources

來源與署名

來源:DuendeSoftware/duende-skills位於skills/oauth-oidc-protocols提交fb32edc

授權條款: 無授權條款

內容歸原作者所有。SourceWeft 從公開儲存庫中收錄這些內容。

檢舉或申請下架