skip to content

Given only an issuer URL, how does a relying party find and use the OpenID Provider configuration document?

level: juniorimportance: must knowfreq 58%

answer

  1. one URL in, many endpoints out
  2. a well-known path under the issuer
  3. append it, never insert it
  4. terminating slash removed first
  5. issuer path kept, segment goes after

basics

~10 s

Remove any terminating slash from the issuer URL, then concatenate /.well-known/openid-configuration to it. The JSON object returned carries issuer, authorization_endpoint, token_endpoint, userinfo_endpoint and jwks_uri, so one configured value yields every address the flow needs.

solid answer

~40 s

OpenID Connect Discovery turns one configured value into a whole integration. The Issuer Identifier is an `https` URL with no query or fragment; you strip any terminating `/` and **concatenate** `/.well-known/openid-configuration` to it, so an issuer of `https://sso.county.example/schools` is fetched at `https://sso.county.example/schools/.well-known/openid-configuration` — the issuer's own path component stays, and the well-known segment goes after it. That is deliberate: appending rather than inserting lets one host carry several independent issuers. The response is a JSON object served as `application/json`. From it a relying party reads `issuer`, `authorization_endpoint`, `token_endpoint`, `userinfo_endpoint`, `jwks_uri` and `registration_endpoint`, plus the `*_supported` capability members. It is still configured with its own `client_id` and redirect URI; discovery supplies the provider's side.

code

http · 6 lines
http
GET /schools/.well-known/openid-configuration HTTP/1.1
Host: sso.county.example
Accept: application/json

HTTP/1.1 200 OK
Content-Type: application/json

go deeper

for a junior

Recall the shape: one issuer URL in configuration, and /.well-known/openid-configuration concatenated to it at runtime. Be able to name what comes back - the endpoint URLs and the jwks_uri.

for a middle

Explain why the rule is concatenation and not insertion: several issuers can share one host, each with its own document under its own path. Separate the required members from the merely recommended ones.

for a senior

Show that you validate the document rather than trusting it. Say what you do when a recommended member is missing, and how a browser-based client's cross-origin constraint changes which providers actually work.

for a principal

Frame it as a runtime contract with an external party: your deployment configuration shrinks to one value per tenant, and in exchange your availability now depends on a document you fetch from someone else's host.

## The problem discovery solves A relying party — the application a user signs in to — needs several absolute URLs before an OpenID Connect flow can start: where to send the browser, where to exchange an authorization code, where to read profile claims, and where the provider publishes the key set its signatures are verified against. Hardcoding those is how an integration rots. A provider moves an endpoint, or a second provider is added, and the deployment configuration grows half a dozen entries per tenant that nobody dares change. OpenID Connect Discovery collapses them into **one** configured value — the **Issuer Identifier** — plus one fetch at runtime. A booking system for a school's parents' evening that accepts sign-in from the local authority running the accounts is handed exactly that and nothing else: an issuer URL. ## Building the request URL The Issuer Identifier is a URL using the `https` scheme, carrying no query component and no fragment component. The configuration document's URL is formed by **concatenating** `/.well-known/openid-configuration` to the Issuer Identifier, with any terminating `/` removed first. | Issuer Identifier | Configuration document URL | |---|---| | `https://sso.county.example` | `https://sso.county.example/.well-known/openid-configuration` | | `https://sso.county.example/` | `https://sso.county.example/.well-known/openid-configuration` | | `https://sso.county.example/schools` | `https://sso.county.example/schools/.well-known/openid-configuration` | The third row is the one candidates get wrong. The issuer's path component is **kept**, and the well-known segment goes **after** it. The specification gives the reason plainly: appending rather than inserting is what lets a single host publish several independent issuers, each with its own document, without them colliding at the host root. The well-known URI space itself is the one registered by `RFC 5785`. It is worth holding that shape in mind because the neighbouring OAuth authorization-server metadata document defined by `RFC 8414` is built the other way round — its well-known string is **inserted** between the host and the issuer's path, giving a URL under `/.well-known/oauth-authorization-server`. Same deployment, two documents, two differently-shaped URLs. ## What comes back A `200` response whose body is a JSON object, served with the media type `application/json`. JSON here is `RFC 8259` JSON — no wrapper, no signature envelope, no XML. The endpoint SHOULD also support cross-origin requests, because a client running in a browser has to be able to read it directly. The specification separates what a conforming document **MUST** carry from what it merely **SHOULD**: - **REQUIRED**: `issuer`, `authorization_endpoint`, `jwks_uri`, `response_types_supported`, `subject_types_supported`, `id_token_signing_alg_values_supported`. `token_endpoint` is required too for any provider that issues an authorization code to be exchanged. - **RECOMMENDED**: `userinfo_endpoint`, `registration_endpoint`, `scopes_supported`, `claims_supported`. - **OPTIONAL**: the long tail of `*_supported` capability members — `grant_types_supported`, `token_endpoint_auth_methods_supported`, `claims_parameter_supported`, `request_parameter_supported`, `request_uri_parameter_supported`, `display_values_supported`, `ui_locales_supported`, `claims_locales_supported` and the rest. ## The members a relying party bootstraps from 1. `issuer` — the provider's own name for itself. It is not decoration: it exists to be compared against the URL you used to build the request and against the `iss` claim of ID tokens later. 2. `authorization_endpoint` — where the browser is sent to start the flow. 3. `token_endpoint` — where the code is exchanged, server to server. 4. `userinfo_endpoint` — where claims about the signed-in user can be read. 5. `jwks_uri` — the URL at which the provider publishes its key set. Discovery's job ends at **advertising** that URL; what a verifier then does with the keys belongs to token validation, not to the configuration document. 6. `registration_endpoint` — where a client may register itself, when the provider allows it. ## Three things this is not - **Not resolving which provider a user belongs to.** Discovery here means fetching a known provider's configuration. Working out which provider a given person should be sent to is a different job with a different mechanism. - **Not the OAuth authorization-server metadata document.** Different well-known string, opposite construction rule. - **Not a substitute for checking what came back.** Fetching over `https` tells you the connection was to the host you named; it does not by itself tell you the JSON describes the issuer you meant. That comparison is a step of its own. The practical payoff is the one a first-screen interviewer is listening for: one value in configuration, everything else read at runtime from a document the provider controls.

  • The issuer URL you were given ends in a slash. What exactly do you request?
    Remove the terminating `/` first, then concatenate. An issuer of `https://sso.county.example/` is fetched at `https://sso.county.example/.well-known/openid-configuration`, with a single slash before the well-known segment. Keeping the trailing slash and appending would produce a doubled slash and a different URL, and the `issuer` member you compare against afterwards is the form without it.
  • Which members must a conforming OpenID Provider configuration document carry, and which are only recommended?
    REQUIRED: `issuer`, `authorization_endpoint`, `jwks_uri`, `response_types_supported`, `subject_types_supported` and `id_token_signing_alg_values_supported`, plus `token_endpoint` for any provider issuing a code to exchange. RECOMMENDED, and therefore legitimately absent: `userinfo_endpoint`, `registration_endpoint`, `scopes_supported` and `claims_supported`. Treating a recommended member's absence as a broken provider is a common misreading.
  • What media type must the response carry, and why does a client running in a browser care?
    The document is returned as `application/json`. A browser-based relying party has an additional constraint: the fetch is cross-origin, so the specification says the endpoint SHOULD support cross-origin requests. A provider that omits the relevant response headers is readable from a server but not from page script, which is why the same issuer can work for one client shape and not another.

A building with several tenants puts a noticeboard just inside each tenant's own door rather than one board at the street entrance. That is why the well-known segment goes after the issuer's path: one host, many issuers, no collision.

saying these in an interview costs you the question

  • Thinks the well-known segment replaces the issuer's own path component
  • Assumes every provider publishes the document at the host root
  • Confuses it with the RFC 8414 authorization-server metadata path
  • Expects an HTML page or a redirect rather than application/json
  • Thinks discovery means working out which provider a user belongs to
  • Keeps the issuer's trailing slash and requests a doubled-slash URL