Skip to main content

OAuth2.0 & OpenID Connect well-known endpoint discovery

There are two types of well-known discovery endpoints. The OAuth2.0 and the OpenID Connect (OIDC) discovery endpoints. Both endpoints return nearly identical information as OIDC is built on top of OAuth 2.0 (OIDC is a superset of OAuth 2.0).

The discovery endpoints provide a standardized way for clients to automatically discover your server's configuration and capabilities. They eliminate manual configuration, enable dynamic multi-tenant support, and make OAuth 2.0 and ODIC integrations more resilient to changes. Discovery endpoints allow clients to adapt automatically to configuration changes without code updates.

OAuth 2.0 discovery endpoint

Use the OAuth 2.0 oauth-authorization-server when you're implementing pure OAuth 2.0 (authorization only). Example use cases are when you need: - API access tokens - Machine-to-machine authentication - Resource server authorization - Client credentials flow - No user identity information needed

OIDC discovery endpoint

Use the OIDC openid-configuration when you're implementing OpenID Connect (authentication + authorization). Example use cases are when you need: - User login/authentication - User profile information - ID tokens - Single sign-on (SSO) - Other sign in flows

Click the appropriate tab below to learn more about the OAuth2.0 or OIDC discovery endpoint.

What is the OAuth 2.0 discovery endpoint?

The discovery endpoint returns a JSON document containing metadata about your OAuth 2.0 authorization server, including:

  • Available endpoints (authorization, token, userinfo, etc.)
  • Supported grant types and response types
  • Supported authentication methods
  • Available scopes
  • Security capabilities (PKCE, token revocation, etc.)

Implementation with Ory

Ory Network

Ory Network automatically provides the discovery endpoint for your project:

# Replace with your project slug
curl https://your-project.projects.oryapis.com/.well-known/oauth-authorization-server

Ory Hydra (Self-hosted)

Ory Hydra exposes the discovery endpoint by default:

# Using default Hydra configuration
curl http://127.0.0.1:4444/.well-known/oauth-authorization-server

When configuring your Ory Hydra instance, ensure the issuer URL is set correctly:

# hydra.yml
urls:
self:
issuer: https://your-auth-server.com

How it works

The discovery endpoint enables automatic configuration for OAuth 2.0 clients:

  1. Client makes a single request to the discovery endpoint
  2. Server returns metadata describing all endpoints and capabilities
  3. Client configures itself using the discovered information
  4. Client performs authentication using the discovered endpoints

Example discovery requests

curl https://your-project.projects.oryapis.com/.well-known/oauth-authorization-server | jq

Example response

{
"issuer": "https://your-project.projects.oryapis.com",
"authorization_endpoint": "https://your-project.projects.oryapis.com/oauth2/auth",
"device_authorization_endpoint": "https://your-project.projects.oryapis.com/oauth2/device/auth",
"token_endpoint": "https://your-project.projects.oryapis.com/oauth2/token",
"jwks_uri": "https://your-project.projects.oryapis.com/.well-known/jwks.json",
"subject_types_supported": [
"public"
],
"response_types_supported": [
"code",
"code id_token",
"id_token",
"token id_token",
"token",
"token id_token code"
],
"claims_supported": [
"sub"
],
"grant_types_supported": [
"authorization_code",
"implicit",
"client_credentials",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code"
],
"response_modes_supported": [
"query",
"fragment",
"form_post"
],
"userinfo_endpoint": "https://your-project.projects.oryapis.com/userinfo",
"scopes_supported": [
"offline_access",
"offline",
"openid"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic",
"private_key_jwt",
"none"
],
"userinfo_signing_alg_values_supported": [
"none",
"RS256"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"id_token_signed_response_alg": [
"RS256"
],
"userinfo_signed_response_alg": [
"RS256"
],
"request_parameter_supported": true,
"request_uri_parameter_supported": true,
"require_request_uri_registration": true,
"claims_parameter_supported": false,
"revocation_endpoint": "https://your-project.projects.oryapis.com/oauth2/revoke",
"backchannel_logout_supported": true,
"backchannel_logout_session_supported": true,
"frontchannel_logout_supported": true,
"frontchannel_logout_session_supported": true,
"end_session_endpoint": "https://your-project.projects.oryapis.com/oauth2/sessions/logout",
"request_object_signing_alg_values_supported": [
"none",
"RS256",
"ES256"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"credentials_endpoint_draft_00": "https://your-project.projects.oryapis.com/credentials",
"credentials_supported_draft_00": [
{
"format": "jwt_vc_json",
"types": [
"VerifiableCredential",
"UserInfoCredential"
],
"cryptographic_binding_methods_supported": [
"jwk"
],
"cryptographic_suites_supported": [
"PS256",
"RS256",
"ES256",
"PS384",
"RS384",
"ES384",
"PS512",
"RS512",
"ES512",
"EdDSA"
]
}
]
}

Use cases

Automatic client configuration

Instead of manually configuring every endpoint, clients can discover them automatically:

// Fetch discovery document
const response = await fetch("https://your-project.projects.oryapis.com/.well-known/oauth-authorization-server")
const config = await response.json()

// Use discovered endpoints
const authUrl = config.authorization_endpoint
const tokenUrl = config.token_endpoint

Multi-tenant applications

Support multiple identity providers without hardcoding configurations:

async function configureOAuth(tenantUrl) {
const discovery = await fetch(`${tenantUrl}/.well-known/oauth-authorization-server`).then((r) => r.json())

return {
authEndpoint: discovery.authorization_endpoint,
tokenEndpoint: discovery.token_endpoint,
supportsPKCE: discovery.code_challenge_methods_supported?.includes("S256"),
}
}

// Works with any OAuth 2.0 compliant server
await configureOAuth("https://tenant-a.oryapis.com")
await configureOAuth("https://tenant-b.oryapis.com")

Capability detection

Check what features the authorization server supports before using them:

const discovery = await fetch(discoveryUrl).then((r) => r.json())

// Check if server supports PKCE
const supportsPKCE = discovery.code_challenge_methods_supported?.includes("S256")

// Check available grant types
const supportsRefreshTokens = discovery.grant_types_supported?.includes("refresh_token")

// Adapt your implementation accordingly
if (supportsPKCE) {
// Use authorization code flow with PKCE
} else {
// Fall back to basic authorization code flow
}

Building discovery-aware clients

Step 1: Fetch discovery document

async function initializeOAuthClient(issuerUrl) {
const discoveryUrl = `${issuerUrl}/.well-known/oauth-authorization-server`
const config = await fetch(discoveryUrl).then((r) => r.json())

return config
}

Step 2: Store configuration

const oauthConfig = await initializeOAuthClient("https://auth.example.com")

// Store for later use
localStorage.setItem("oauth_config", JSON.stringify(oauthConfig))

Step 3: Use discovered endpoints

// Authorization
window.location.href = `${oauthConfig.authorization_endpoint}?
client_id=${clientId}&
response_type=code&
redirect_uri=${redirectUri}&
scope=openid profile email`

// Token exchange
const tokenResponse = await fetch(oauthConfig.token_endpoint, {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
grant_type: "authorization_code",
code: authCode,
client_id: clientId,
redirect_uri: redirectUri,
}),
})

Key metadata fields

Required fields

  • issuer: Identifier of the authorization server
  • authorization_endpoint: URL for authorization requests
  • token_endpoint: URL for token requests
  • jwks_uri: URL for public keys (token verification)

Common optional fields

  • userinfo_endpoint: URL to get user information (OIDC)
  • revocation_endpoint: URL to revoke tokens
  • introspection_endpoint: URL to validate tokens
  • registration_endpoint: URL for dynamic client registration
  • scopes_supported: List of supported OAuth 2.0 scopes
  • response_types_supported: Supported response types
  • grant_types_supported: Supported grant types
  • token_endpoint_auth_methods_supported: Client authentication methods
  • code_challenge_methods_supported: PKCE methods (S256, plain)

Best practices

Cache discovery results

Discovery documents change infrequently. You can cache them to reduce latency:

const CACHE_KEY = "oauth_discovery"
const CACHE_DURATION = 24 * 60 * 60 * 1000 // 24 hours

async function getDiscoveryDocument(issuerUrl) {
const cached = localStorage.getItem(CACHE_KEY)
if (cached) {
const { data, timestamp } = JSON.parse(cached)
if (Date.now() - timestamp < CACHE_DURATION) {
return data
}
}

const discovery = await fetch(`${issuerUrl}/.well-known/oauth-authorization-server`).then((r) => r.json())

localStorage.setItem(
CACHE_KEY,
JSON.stringify({
data: discovery,
timestamp: Date.now(),
}),
)

return discovery
}

Handle errors gracefully

async function fetchDiscovery(issuerUrl) {
try {
const response = await fetch(`${issuerUrl}/.well-known/oauth-authorization-server`)

if (!response.ok) {
throw new Error(`Discovery failed: ${response.status}`)
}

return await response.json()
} catch (error) {
console.error("Failed to fetch discovery document:", error)
// Fall back to manual configuration or show error to user
throw error
}
}

Validate required fields

function validateDiscovery(config) {
const required = ["issuer", "authorization_endpoint", "token_endpoint", "jwks_uri"]

for (const field of required) {
if (!config[field]) {
throw new Error(`Missing required field: ${field}`)
}
}

return true
}

Troubleshooting

Discovery endpoint returns 404

Verify the URL format is correct:

  • OAuth 2.0: /.well-known/oauth-authorization-server

For Ory Network, ensure you're using your project's full URL:

https://your-project.projects.oryapis.com/.well-known/oauth-authorization-server

CORS issues

If calling the discovery endpoint from a browser, ensure CORS is properly configured on your authorization server. Ory Network handles this automatically. For self-hosted Ory Hydra, configure CORS in your configuration:

# hydra.yml
serve:
public:
cors:
enabled: true
allowed_origins:
- https://your-app.com

Cached stale data

If endpoints have changed but clients are using old configuration, clear the discovery cache or reduce cache duration.