Discovery and the .well-known Endpoints
A client that knows nothing but an issuer URL can find every address it needs: endpoint URLs, signing keys, supported scopes, response types and algorithms all come from one JSON document fetched from a predictable path. Its own credentials and redirect URIs it still has to be told. 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. Learning that late costs an integration: clients cannot compute the address, and every one of them ends up carrying a hand-written endpoint list, which is the thing discovery exists to remove.
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. The suffixes that matter here are registered in the IANA Well-Known URIs registry, each with its own defining specification: openid-configuration, oauth-authorization-server, oauth-protected-resource, and many outside OAuth entirely.
RFC 8615 asks that a minted well-known URI be registered and discourages squatting on generic terms, so a path under the prefix that is not in the registry is a local convention, and no stranger can compute it.
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 one of the two reasons a perfectly healthy server appears to have no metadata. The other is the path rule below.
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 disagreement is not arbitrary. RFC 8615, section 3 defines a well-known URI as one rooted at the top of the path hierarchy: /.well-known/example is one, /foo/.well-known/example is not. OpenID Connect Discovery concedes the same limit about its own rule, noting that the syntax and semantics of .well-known apply to the issuer value when it contains no path component. RFC 8414 inserts rather than concatenates so that the result stays a well-known URI while still supporting 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 which of them a given provider answers depends on how its routes are built rather than on which specification it claims to follow.
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, since assuming is how this one bites. 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. Caching it is not optional, and three details decide whether the cache is safe.
- Cache in the application, on your own timer. Metadata endpoints commonly answer with
Cache-Control: no-store, Abblix OIDC Server included, so an HTTP cache will not do this for you. Hours rather than seconds, and never per request. - Give
jwks_uriits own policy. On a token whosekidis not in the cached key set, refetch once, and rate-limit that path so an unknownkidcannot turn into a request flood. Without the refetch, the provider's next key rotation is your outage. - Bound the staleness. Serving the last good copy through a fetch failure is right; serving it indefinitely is not, because a key rotated away after a compromise would stay trusted. Pick a maximum age past which a failed refresh becomes an error.
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
Abblix OIDC Server is certified by the OpenID Foundation across all login and logout profiles, discovery included. The library serves the same document at both suffixes: the default routes publish it at /.well-known/openid-configuration and at /.well-known/oauth-authorization-server, with the key set at /.well-known/jwks. RFC 8414 permits publishing at more than one well-known location, so a client written against either specification finds an issuer that has no path component. Both are served with CORS, and both answer Cache-Control: no-store, which is why the caching advice above is about the application's cache and not the browser's.
That third path is the deployment's own convention rather than a registered suffix. Clients reach the key set through the jwks_uri member of the document, so the address is free to move.
For an issuer with a path, the concatenated form falls out of mounting the endpoints under a matching prefix: with the OIDC endpoints mapped under /tenant1, both documents are served under /tenant1/.well-known/, which is the address an OpenID Connect client computes. The RFC 8414 insertion form, /.well-known/oauth-authorization-server/tenant1, is not produced by any prefix, because a prefix prepends to every route in the group. It is set as an explicit path instead, through the route options on Minimal API or the matching route token on MVC. A deployment that expects both kinds of client sets both, which RFC 8414 section 3 permits.
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. Plenty of servers insert instead. Test the actual URL.
- Serving the document without CORS. OpenID Connect Discovery says the
openid-configurationendpoint SHOULD support it, because browser-based clients read the document directly. RFC 8414 does not address the question, so a server written to it alone can be perfectly conformant and still unreachable from a browser. Serve it on both suffixes and onjwks_uri. - Fetching it on every request, or caching it forever. The three caching rules above cover both.