Skip to content

OIDC discovery

Discovery is the OIDC convention for "here is everything you need to know about this identity provider, in one JSON document". Your SDK fetches it once on startup and caches the result; from then on, it knows every endpoint and every supported capability without hard-coded URLs.

GET https://<your-tenant-url>/.well-known/openid-configuration

No authentication required. Public by design — a relying party that wants to integrate needs the discovery document before it has any credentials.

{
"issuer": "https://banking-cymmetri.intelliauth.local",
"authorization_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/authorize",
"token_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/token",
"userinfo_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/userinfo",
"jwks_uri": "https://banking-cymmetri.intelliauth.local/.well-known/jwks.json",
"revocation_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/revoke",
"introspection_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/introspect",
"device_authorization_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/device_authorization",
"end_session_endpoint": "https://banking-cymmetri.intelliauth.local/oauth2/logout",
"response_types_supported": ["code", "id_token", "code id_token"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["RS256"],
"scopes_supported": ["openid", "profile", "email", "offline_access"],
"token_endpoint_auth_methods_supported": [
"client_secret_basic",
"client_secret_post",
"none"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"client_credentials",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:token-exchange"
],
"code_challenge_methods_supported": ["S256"]
}

On boot, the SDK fetches discovery and caches it. Then:

  • authorization_endpoint → where to send the browser for sign-in.
  • token_endpoint → where to POST for token exchange.
  • jwks_uri → where to fetch public keys for verifying id tokens.
  • userinfo_endpoint → where to fetch claim data for the current user.
  • revocation_endpoint → where to revoke tokens on sign-out.
  • end_session_endpoint → where to redirect for tenant-side logout.

All hard-coded URLs in your codebase become a single hard-coded value (tenantUrl), with everything else derived from discovery.

The other "well-known" document is the key set:

GET https://<your-tenant-url>/.well-known/jwks.json

Response:

{
"keys": [
{
"kty": "RSA",
"kid": "key-2026-q2",
"use": "sig",
"alg": "RS256",
"n": "...base64url-modulus...",
"e": "AQAB"
}
]
}

When verifying an id token, your code reads the kid from the JWT header, picks the matching key from this document, and verifies the signature. Cache the document; refresh on kid miss.

Discovery and JWKS responses include Cache-Control headers. Honour them. The typical pattern:

  • Cache discovery for 24 hours; refresh on startup.
  • Cache JWKS for 24 hours, but refresh immediately when an id token carries a kid not in the cache.

Refreshing on kid miss is how the platform rotates signing keys without forcing every relying party to coordinate the change.

You almost never do. The SDK reads it for you. The case where you do read it: setting up federation with another identity provider — see the federation topic. The remote IDP exposes the same shape of document, and you point IntelliAuth at it.