Endpoints
Every path this server answers, what each is for, and the switches that turn them off. The code on this page is
included from samples/Server.Hosting,
which a test boots and asks for its discovery document. Setting the server up is getting started;
this page is what that setup serves.
The pipeline
var app = builder.Build();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer(); // every protocol endpoint, at the host's root: /.well-known/*, /connect/*
app.UseAuthorization();
app.MapRazorPages(); // the sign-in, consent, sign-out, device, backchannel and error pages
app.Run();
UseIdentityServer serves the protocol
endpoints at fixed paths, /.well-known/* and /connect/*. Before it adds anything to the pipeline it refuses to
start a server that could not work: no authentication scheme to sign users in with, a session scheme named in
Authentication.CookieAuthenticationScheme that is not registered, automatic key management off with no signing
credential, a signing algorithm automatic management cannot generate, CIBA switched on without the host's half of it,
an extension grant that could never run (one named like a built-in grant, or two validators claiming one
grant type), pushed authorization required server-wide with its endpoint off, and a negative
ConsumedRefreshTokenGracePeriod. It then applies the protocol endpoints' CORS policy by name, so the host's
own CORS configuration is untouched, and serves the endpoints.
The interactive pages — sign-in, consent, sign-out, device and backchannel approval, the error page — are the
Fdyay.IdentityServer.UI Razor class library's, mapped by MapRazorPages. Where the server sends a browser for each is
UserInteractionOptions.
Every path
Each protocol endpoint is registered by AddIdentityServer() whatever the options say, and the router reads
EndpointsOptions per request, so a switch set after AddIdentityServer()
still takes effect. A switched-off endpoint falls through the middleware — a 404 in a host with nothing else at that
path — and drops out of discovery.
| Path | What it is | Switch | Recorded flow |
|---|---|---|---|
/.well-known/openid-configuration |
OpenID Connect discovery: the document every client starts from | EnableDiscoveryEndpoint |
Discovery and JWKS |
/.well-known/openid-configuration/jwks |
The JSON Web Key Set: the public half of every signing key, current and retiring | EnableDiscoveryEndpoint, and Discovery.ShowKeySet |
Discovery and JWKS |
/.well-known/oauth-authorization-server |
The same document as OAuth 2.0 authorization server metadata (RFC 8414), below | EnableOAuth2MetadataEndpoint |
— |
/connect/authorize |
The authorization endpoint: a browser arrives, signs in, consents, and leaves with a code | EnableAuthorizeEndpoint |
Authorization code with PKCE |
/connect/token |
The token endpoint: every grant — code, refresh, client credentials, device, CIBA, token exchange, extension grants | EnableTokenEndpoint |
Client credentials, refresh, token exchange, resource indicators, DPoP |
/connect/userinfo |
The signed-in user's claims, for a bearer of an access token with openid |
EnableUserInfoEndpoint |
— |
/connect/introspect |
Token introspection (RFC 7662), for an API holding a reference token or checking a JWT is still live | EnableIntrospectionEndpoint |
Token introspection |
/connect/revocation |
Token revocation (RFC 7009) | EnableTokenRevocationEndpoint |
Token revocation |
/connect/endsession |
RP-initiated logout: ends the session here and tells the clients it reached | EnableEndSessionEndpoint |
RP-initiated logout |
/connect/checksession |
The session management iframe a relying party embeds to notice a sign-out (why it may be framed) | EnableCheckSessionEndpoint |
— |
/connect/par |
Pushed authorization requests (RFC 9126) | EnablePushedAuthorizationEndpoint |
PAR |
/connect/deviceauthorization |
The device authorization grant (RFC 8628); the user approves at /device |
EnableDeviceAuthorizationEndpoint |
Device authorization |
/connect/ciba |
Client-initiated backchannel authentication — off by default, until a host supplies the two services only it can | EnableBackchannelAuthenticationEndpoint |
CIBA |
/connect/mtls/token and the other aliases |
The endpoints a client authenticates at, again under a path a listener can demand a certificate on — see client certificates | MutualTls.Enabled |
— |
/saml/sso |
SAML 2.0 single sign-on, service-provider initiated (with AddSaml) |
EnableSamlSigninEndpoint |
SAML 2.0 single sign-on |
/saml/sso/callback |
Where a SAML sign-on resumes after the sign-in page | EnableSamlSigninCallbackEndpoint |
SAML 2.0 single sign-on |
/saml/sso/initiate |
IdP-initiated SAML sign-on — off until SamlOptions.EnableIdpInitiatedSignOn |
EnableSamlSigninEndpoint |
— |
/saml/slo |
SAML 2.0 single logout | EnableSamlLogoutEndpoint |
— |
/saml/metadata |
This server's SAML 2.0 identity provider metadata | EnableSamlMetadataEndpoint |
— |
The four SAML switches are off by default and switched on by AddSaml, as Duende's are; switch one off with a
Configure<IdentityServerOptions> after AddSaml. The sign-on switch closes the IdP-initiated door too. The SAML
sign-on, callback and logout paths move with SamlOptions.Endpoints
(SAML identity provider); the metadata path stays fixed.
The demo host in src/Fdyay.IdentityServer.Host also answers /health; that is the host's own endpoint, not this
library's.
The mTLS aliases are /connect/mtls/token, /connect/mtls/introspect, /connect/mtls/revocation, /connect/mtls/par,
/connect/mtls/deviceauthorization and /connect/mtls/ciba — the endpoints a client calls directly with its
credentials, read from one list by both the router and discovery, so an alias cannot answer without being advertised or
be advertised without answering.
OAuth 2.0 authorization server metadata
New in 0.125.0. RFC 8414's /.well-known/oauth-authorization-server serves the discovery document, for a client
that reads OAuth's well-known path rather than OpenID Connect's — Duende 8.0.1's endpoint, measured. It is on by
default under its own flag, EndpointsOptions.EnableOAuth2MetadataEndpoint; turning discovery off leaves it on, as in
Duende. GET only.
RFC 8414 §3.1 puts an issuer's path after the well-known segment: an issuer https://idp.example.com/tenant
publishes at https://idp.example.com/.well-known/oauth-authorization-server/tenant. So the endpoint answers beneath
its own path, and only when what follows is this server's issuer:
- the path must satisfy IIssuerPathValidator — by default, no path at all, or the issuer's own path, ignoring case; register your own to accept others;
- the issuer must then be exactly the request's scheme, host and that path — so a server whose configured
IssuerUrinames another host publishes no OAuth metadata at this one, while its OpenID document still answers; - and a request under a path base is a 404: RFC 8414's well-known path is at the host's root.
Anything else is a 404, so a client is never handed metadata naming an issuer other than the one it asked for (RFC 8414 §3.3).
Discovery options
// A member of the host's own beside the document's; "~/" makes it a URL under the issuer.
options.Discovery.CustomEntries["service_documentation"] = "~/docs";
// Cache-Control: max-age=3600 on the discovery document, its key set and the OAuth metadata.
options.Discovery.ResponseCacheInterval = 3600;
// Hold the document's JSON in the IDistributedCache for a minute rather than building it per request.
options.Discovery.EnableDiscoveryDocumentCache = true;
options.Discovery.DiscoveryDocumentCacheDuration = TimeSpan.FromMinutes(1);
New in 0.139.0, Duende 8.0.1's DiscoveryOptions, measured from its
options, DiscoveryResponseGenerator and discovery endpoints. Every switch is on by default, as Duende's are; each is
described under DiscoveryOptions. What the reference does not say:
- The three authentication-method lists are one list — the methods the registered secret parsers implement —
because the revocation and introspection endpoints take their caller's credential through the same parsers as the
token endpoint. The client-assertion algorithms go beside a list only when
private_key_jwtis on it andSupportedClientAssertionSigningAlgorithmsis not empty, as Duende's do; until 0.139.0 the token endpoint's were always written. - A custom entry never replaces a member the document already has — it is skipped, with Duende's error logged — so configuration cannot rewrite the issuer or an endpoint every client is told to trust.
- The cached document is the one built for the first request in its window: an option or a resource changed
meanwhile shows when it expires. The cache is the host's
IDistributedCache— the in-memory one by default, which no other instance shares. ResponseCacheIntervalsets themax-ageof the discovery document, the key set and the OAuth metadata alike;0sendsno-store; null (the default) sends noCache-Control.
Changed in 0.139.0:
- The discovery document, the key set and the OAuth metadata no longer send
no-storeby default. They were written as the token endpoint's answers are; a document that carries no secret now carries noCache-ControlunlessResponseCacheIntervalasks for one, as in Duende. Set it to0to keep the old headers. - The key set is a 404 while
ShowKeySetis off, as Duende's is. Until then it was served at a path the document no longer named. - Breaking for a host that constructs them itself:
DiscoveryResponseGeneratortakes anILogger<DiscoveryResponseGenerator>, andDiscoveryEndpoint,DiscoveryKeyEndpointandOAuthMetadataEndpointtakeIOptions<IdentityServerOptions>. Resolved from DI, nothing changes.
Ours: one result writes both documents, where Duende has DiscoveryDocumentResult and JsonWebKeysResult; the cache
is keyed by the issuer alone, which here is also the base of every URL in the document (Duende keys by both). Not taken:
DynamicClientRegistration, which advertises a registration endpoint this server does not have (decision 6 in
docs/surface-remeasure.md).