Table of Contents

OAuth 2.1 posture

This server implements the OAuth 2.1 subset of OAuth 2.0 (RFC 6749, with PKCE from RFC 7636) rather than all of it: authorization code with PKCE, client credentials, refresh token and device code, plus backchannel authentication, token exchange and grants of a host's own. The code on this page is included from samples/Protocols.Resources, which a test boots and reads the discovery document of: no implicit or password grant, and code as the only response type.

What OAuth 2.1 requires, and where

Each row was checked against a call site, not inferred from a type name.

OAuth 2.1 requirement Here
Implicit grant removed Not implemented, and not configurable: GrantTypes.Implicit no longer exists
Resource-owner password (ROPC) removed Not implemented, and not configurable: GrantTypes.ResourceOwnerPassword no longer exists
PKCE required for the authorization code flow RequirePkce defaults to true and is enforced by the authorize validator
Redirect URIs compared by exact string match No wildcard or prefix matching anywhere (see which redirect URIs a client may use)
Bearer tokens never in the query string UserInfo reads the Authorization header only
Refresh tokens rotated / single use RefreshTokenUsage defaults to OneTimeOnly
An artifact is redeemable only by the client it was issued to All five: authorization code, refresh token, device code, auth_req_id, and — as of 0.37.0 — a pushed request_uri

A native client, with each of those defaults written out:

// A native application. Each setting shown is the default, written out: PKCE required and with S256 only,
// and a refresh token that is good for one use. The loopback address is registered without a port.
new Client
{
    ClientId = "field-notes",
    RequireClientSecret = false,
    AllowedGrantTypes = { OidcConstants.GrantTypes.AuthorizationCode },
    RedirectUris = { "http://127.0.0.1" },
    AllowedScopes = { "openid", "profile", "ledger.read", "orders.read" },
    AllowOfflineAccess = true,

    RequirePkce = true,
    AllowPlainTextPkce = false,
    RefreshTokenUsage = IdentityServerConstants.TokenUsage.OneTimeOnly,
},

One deliberate opt-out. AllowPlainTextPkce lets a single client fall back to the plain code challenge method, which OAuth 2.1 disallows. It defaults to false, so it costs a host an explicit per-client decision; it exists for legacy clients that cannot compute S256. Leave it off.

Why the removed grants are gone rather than refused at runtime. Both were once settable and neither was ever serviceable: a client registered for one validated cleanly and could then never obtain a token. Keeping the names while refusing the requests would keep that false promise. The constants are deleted, so the failure is a compile error at configuration time rather than a puzzle at runtime. A client that asks for either anyway is answered unsupported_grant_type (see a grant this server does not have).

Corrected in 0.130.0: the fragment and form_post response modes were deleted with those grants, as the modes only implicit and hybrid need — and they are not: both apply to response_type=code, and ASP.NET Core's OpenID Connect handler asks for form_post by default. They are back; see Response modes.

The flows, as the server runs them

Every flow this server serves is recorded from the demo host, request by request, with a sequence diagram drawn from the same exchanges — see Protocol flows:

Flow Recorded
Discovery and the key set Discovery and JWKS
Authorization code with PKCE — interactive web, SPA and native applications Authorization code with PKCE
Refresh, with rotation Refresh token
Client credentials — machine to machine Client credentials
A token for one API Resource indicators
A service's token for a user Token exchange
An API asking about a token Introspection, revocation
Pushed authorization requests PAR
A device with no browser Device authorization
A user who is somewhere else CIBA (poll mode)
A token bound to the client's key DPoP
Signing out RP-initiated logout
SAML 2.0, this server as the identity provider SAML single sign-on

SAML 2.0 (OASIS SAML Core) is native in both directions, over the HTTP-Redirect and HTTP-POST bindings, and every response and assertion is signed with the framework's XML-DSig. Two SAML shapes are not recorded:

  • IdP-initiated sign-on. The user follows a link at this server (/saml/sso/initiate?sp=…) and there is no AuthnRequest at all; the rest is the recorded flow from the signed response on.
  • This server as the service provider, federating the user out to an upstream identity provider — statically registered (see External providers) or stored as a row (see stored SAML providers):
sequenceDiagram
    autonumber
    participant U as User (Browser)
    participant SP as Fdyay.IdentityServer (SP)
    participant IdP as Business IdP

    U->>SP: Access a protected resource (challenge the SAML scheme)
    SP->>SP: Create AuthnRequest, protect state into RelayState
    SP-->>U: 302 to IdP SSO?SAMLRequest=…&RelayState=…
    U->>IdP: Authenticate at the business IdP
    IdP-->>U: 200 auto-POST form → SP assertion consumer service
    U->>SP: POST SAMLResponse (+ RelayState)
    SP->>SP: Verify signature / issuer / audience / recipient / InResponseTo
    SP->>SP: Map NameID + attributes → ClaimsPrincipal, sign into external cookie
    SP-->>U: 302 to the original return URL