Skip to content

Discovery

Clients learn the OP's endpoints, signing keys, and capabilities by fetching one document. For an OIDC OP that document is OIDC Discovery 1.0's /.well-known/openid-configuration. For a pure-OAuth deployment the equivalent is RFC 8414 (OAuth Authorization Server Metadata) at /.well-known/oauth-authorization-server. Both produce a JSON object listing endpoints, supported algorithms, supported scopes, and feature flags. This page is the beginner's tour of that object.

Specs referenced on this page

Why this exists

Without a discovery document, every RP would need its endpoint URLs, signing algorithms, and feature support hard-coded — and every key rotation or feature flip would mean a code change on every RP. Discovery turns "the OP at https://op.example.com" into a single URL the RP needs to know.

What's in the discovery document

A representative subset of the fields a beginner will actually see and use. Every field below is emitted by this library when the corresponding feature is wired.

FieldWhat it is
issuerThe OP's canonical identifier. The same string appears as iss in every signed artefact. RPs compare it byte-for-byte.
authorization_endpointURL of /authorize — where the browser is redirected to log the user in.
token_endpointURL of /token — where the RP exchanges a code or refresh token.
userinfo_endpointURL of /userinfo — where the RP fetches fresh claims with an access token.
jwks_uriURL of the JWKS — the OP's public signing keys, published as a JWK Set.
introspection_endpointURL of /introspect (RFC 7662) — present when the introspection feature is wired.
revocation_endpointURL of /revoke (RFC 7009) — present when the revocation feature is wired.
end_session_endpointURL of /end_session — where the RP redirects the browser for RP-Initiated Logout.
pushed_authorization_request_endpointURL of /par — present only when the PAR feature (RFC 9126) is enabled.
device_authorization_endpointURL of /device_authorization — present only when the device-code grant is wired.
grant_types_supportedGrant types the OP accepts at /token: authorization_code, refresh_token, client_credentials, …
response_types_supportedResponse types /authorize accepts. The library advertises only code (the OIDC Core "code flow").
response_modes_supportedHow /authorize returns the result: query, fragment, form_post, jwt (JARM).
id_token_signing_alg_values_supportedJWS algorithms used to sign ID Tokens. The library publishes ES256 only because OP signing keys are ECDSA P-256.
request_object_signing_alg_values_supportedJWS algorithms accepted on JAR request= parameters: RS256, PS256, ES256, EdDSA.
token_endpoint_auth_methods_supportedHow confidential RPs authenticate at /token: client_secret_basic, client_secret_post, private_key_jwt, plus tls_client_auth / self_signed_tls_client_auth when mTLS is enabled. Public clients use none in registration metadata, but the discovery advertisement does not list none.
code_challenge_methods_supportedPKCE transformations. The library advertises only S256.
subject_types_supportedpublic and (when op.WithPairwiseSubject(...) is wired) pairwise.
claims_supportedClaims the OP can return. Operator-controlled via op.WithClaimsSupported(...).
scopes_supportedScopes the OP recognises. Includes the standard OIDC scopes plus any registered with op.WithScope(...).
dpop_signing_alg_values_supportedPresent only when the DPoP feature (RFC 9449) is enabled.
require_pushed_authorization_requestsSet to true only under FAPI 2.0 profiles, which mandate PAR.
claims_parameter_supportedDefaults to true; pass op.WithClaimsParameterSupported(false) to stop advertising and honoring OIDC §5.5 claims requests.
authorization_details_types_supportedThe RFC 9396 type values accepted in authorization_details. Present only when op.WithAuthorizationDetailTypes(...) is wired.
grant_management_endpointThe grant management endpoint URL. Present only when op.WithGrantManagement(...) is wired.
grant_management_actions_supportedThe grant management actions the OP accepts (create / replace / merge / query / revoke). Present with grant_management_endpoint.
grant_management_action_requiredEmitted as true only when WithGrantManagement(..., actionRequired=true) — an authorization request must then carry grant_management_action.

When op.WithProtectedResources(...) is wired, the OP additionally serves RFC 9728 protected-resource metadata at /.well-known/oauth-protected-resource (one document per registered resource) — a separate well-known document, not a field on openid-configuration.

_supported fields are advertisements, not policy

A _supported list is what the OP will accept — not what every client must use. A client may use a subset. The OP rejects anything outside the list.

How clients use it

A typical RP SDK does the following on first start:

  1. GET https://op.example.com/.well-known/openid-configuration
  2. Cache the response. Treat the Cache-Control: max-age=… header from the OP as the upper bound.
  3. Pick endpoints, supported algorithms, and supported features out of the cached document.
  4. Refetch when the cache expires, or when JWKS verification fails (the kid is unknown — the OP probably rotated keys).

The library helps here in two ways. First, the discovery document and the JWKS are stable as long as no rotation is in flight: caches at the RP can be long-lived. Second, when the operator is mid-rotation, the embedder signals it via op.WithJWKSRotationActive(predicate) — the library then returns a short Cache-Control: max-age on the JWKS so RPs that re-fetch see the new key. The discovery document itself is built once at op.New time, not per request, so its content does not drift across instances.

Discovery is RP-side cache, not OP-side cache

The OP builds and marshals the discovery document when the provider is mounted, then serves the cached JSON body for the handler's lifetime. The expensive operation — JWS verification of an ID Token using a recently-rotated key — is what discovery cache invalidation is really about. See JWKS rotation.

How this library builds it

Discovery is constructed once at op.New (in internal/discovery/build.go) from the wired feature set. There is no runtime drift: if the embedder did not wire PAR, pushed_authorization_request_endpoint is absent and require_pushed_authorization_requests does not appear. If the embedder wired op.WithProfile(profile.FAPI2Baseline), the FAPI narrowing applies — token_endpoint_auth_methods_supported is filtered to the FAPI-permitted set, require_pushed_authorization_requests=true appears, and the introspection / revocation auth-method lists are copied from the same narrowed list.

A discovery golden test in the repository asserts the document shape per profile so silent drift between "what the discovery says" and "what handlers actually do" is caught at PR time. See design judgment #12 for the rationale.

What the library does NOT advertise

The library narrows the discovery document deliberately:

  • No none alg. id_token_signing_alg_values_supported and request_object_signing_alg_values_supported never contain none. The underlying type does not include it (see JOSE basics and design judgment #11).
  • No HMAC-family alg for ID Tokens. HS256 / HS384 / HS512 are not advertised. The alg-confusion attack class needs them to be reachable; closing the type closes the attack.
  • No request_uri indication when PAR is off. request_uri_parameter_supported and pushed_authorization_request_endpoint only appear when the embedder wired PAR.
  • claims_parameter_supported defaults to true, but can be disabled. Passing op.WithClaimsParameterSupported(false) makes the discovery document omit OIDC §5.5 support and makes authorize / PAR ignore claims payloads after malformed JSON has been rejected.
  • No frontchannel_logout_supported or check_session_iframe. Front-Channel Logout 1.0 / Session Management 1.0 are not implemented; the discovery document is silent about them. Embedders use Back-Channel Logout 1.0 instead — see design judgment #5.

Discovery is a contract surface

Every _supported array is a thing the OP commits to accept. Adding values widens the contract; removing values narrows it. RPs may compare against the list to refuse to talk to an OP that no longer advertises an algorithm they require. Treat changes to the discovery document the way you treat changes to a public API.