Discovery and the .well-known Endpoints
A client that knows nothing but an issuer URL can configure itself completely: endpoint addresses, signing keys, supported scopes, response types and algorithms all come from one JSON document fetched from a predictable path. That is what discovery is for, and it is why a well-run deployment has no hand-entered endpoint URLs anywhere in it.
Two specifications define that predictable path, they define it differently, and the difference only shows up once the issuer identifier has a path component. That is the single most expensive thing to learn late about .well-known.
What .well-known is
/.well-known/ is a reserved path prefix for machine-readable metadata about a service, registered by RFC 8615. The point is that a client holding only a base address can find configuration without a second address being agreed in advance. Everything below that prefix is a registered suffix with its own defining specification: openid-configuration, oauth-authorization-server, oauth-protected-resource, and many outside OAuth entirely.
Two suffixes matter here.
| Suffix | Defined by | Describes |
|---|---|---|
openid-configuration | OpenID Connect Discovery 1.0 | an OpenID Provider |
oauth-authorization-server | RFC 8414 | an OAuth 2.0 authorization server |
They describe the same server from two angles, and the field sets overlap almost entirely. A client that knows only one suffix will not find a provider that serves only the other, which is the usual reason a perfectly healthy server appears to have no metadata.
The path rule, and where the two disagree
For an issuer with no path component, both specifications produce the same shape and nobody notices there is a rule:
issuer: https://op.example.com
OIDC: https://op.example.com/.well-known/openid-configuration
RFC 8414: https://op.example.com/.well-known/oauth-authorization-serverGive the issuer a path, as every multi-tenant deployment eventually does, and the two rules pull apart:
issuer: https://op.example.com/tenant1
OIDC Discovery, section 4: CONCATENATE the suffix to the issuer
https://op.example.com/tenant1/.well-known/openid-configuration
RFC 8414, section 3: INSERT the well-known string between host and path
https://op.example.com/.well-known/oauth-authorization-server/tenant1OpenID Connect Discovery, section 4 requires the document at the path formed by concatenating /.well-known/openid-configuration to the issuer. RFC 8414, section 3 requires it at the path formed by inserting the well-known string between the host component and the path component, with any trailing / removed first. The insertion form exists precisely to support several issuers on one host.
There is a third address a client may legitimately try. RFC 8414 notes that an OAuth application may use the suffix openid-configuration while following RFC 8414's own path rule, since despite its name that suffix refers to a general OAuth feature rather than an OpenID-specific one. So a client probing a path-bearing issuer can reasonably ask for any of three URLs, and a provider that implements only the concatenated form answers one of them.
The practical rule for anyone running a provider: if your issuer has a path, serve every form you expect clients to try, and check what your library actually routes rather than what you assume. If your issuer has no path, the question never arises, which is a good argument for keeping it that way.
What the document contains
OpenID Provider Metadata is a flat JSON object. A handful of members carry most of the weight:
issuer, which must equal the issuer you started from. A mismatch is a redirect to somebody else's metadata, and validating it is the whole reason the document is trustworthy.authorization_endpoint,token_endpoint,userinfo_endpoint,end_session_endpoint: where to send each request.jwks_uri: where the signing keys live, so key rotation costs the client nothing.scopes_supported,response_types_supported,grant_types_supported,id_token_signing_alg_values_supported: what the server will accept.code_challenge_methods_supported: which PKCE methods exist. The security BCP recommends publishing this element specifically so clients can detect PKCE support before relying on it.
A client fetches the document, validates issuer, and configures itself from the rest. It should also cache it: the document changes rarely, and fetching it on every request turns a convenience into a dependency.
WebFinger, the other half
Discovery in the specification covers two mechanisms, and the metadata document is only the second. The first is issuer discovery: given a user-supplied identifier such as an email address, WebFinger resolves it to the issuer that serves that user. It answers "which provider does this person belong to", which the metadata document cannot, since fetching it already requires knowing the issuer.
In practice most deployments never need it: the client knows its provider. WebFinger earns its place where the provider is not fixed in advance, such as a service that accepts identities from many organisations.
In Abblix OIDC Server
The library serves both suffixes. IConfigurationHandler builds the discovery document, and the default routes place it at /.well-known/openid-configuration with the same content also published at /.well-known/oauth-authorization-server, alongside the key set at /.well-known/jwks. Publishing at more than one well-known location is explicitly permitted by RFC 8414, and it removes the class of failure where a client written against one specification cannot find a server written against the other.
The paths are options rather than constants. OidcRouteOptions carries each one, so a deployment that has to match an existing address can move them without touching the handlers.
Mistakes worth avoiding
- Not validating
issuerin the response. The document tells the client where to send credentials. Accepting it without checking that it describes the issuer you asked about defeats the point of fetching it from a derived path. - Hardcoding endpoints "just for now". The endpoint set is the one thing discovery exists to remove from your configuration, and a hardcoded
jwks_uriturns the provider's next key rotation into your outage. - Assuming the concatenated path for a path-bearing issuer. Half the ecosystem inserts instead. Test the actual URL.
- Serving the document without CORS. Both specifications say the endpoint should support CORS, because browser-based clients read it directly.
- Fetching it on every request. Cache it, with a refresh interval, and treat a fetch failure as a reason to keep using the last good copy rather than to fail the login.