> For the complete documentation index, see [llms.txt](https://oten.gitbook.io/identity-support/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://oten.gitbook.io/identity-support/integration/prerequisites/discovery-configuration.md).

# Discovery Configuration

This guide shows you how to automatically configure your application using Oten IDP's OpenID Connect Discovery endpoint instead of hardcoding configuration values.

**Need context?** Check the [Configuration Reference](broken://pages/XsyPoCRDPUpz0vcP6KkD) for complete endpoint details.

## What You'll Learn

In this guide, you will:

* Use OpenID Connect Discovery to automatically fetch configuration
* Implement dynamic configuration loading in different programming languages
* Handle configuration caching and error scenarios
* Validate your configuration setup

## Why Use Discovery?

### Benefits of Discovery Configuration

✅ **Automatic Updates**: Configuration changes are picked up automatically\
✅ **Environment Agnostic**: Same code works across dev/staging/production\
✅ **Reduced Errors**: No hardcoded URLs that can become outdated\
✅ **Standards Compliant**: Uses OpenID Connect Discovery specification\
✅ **Future Proof**: New features are automatically available

### Discovery vs Manual Configuration

```javascript
// ❌ Manual configuration (not recommended)
const config = {
  authorizationEndpoint: 'https://account.sbx.oten.dev/v1/oauth/authorize',
  tokenEndpoint: 'https://account.sbx.oten.dev/v1/oauth/token',
  // ... hardcoded values that may change
};

// ✅ Discovery configuration (recommended)
const response = await fetch('https://account.sbx.oten.dev/.well-known/openid-configuration');
const config = await response.json();
// Automatically gets latest configuration
```

## Quick Start

### Basic Discovery Implementation

```javascript
class OtenDiscovery {
  constructor(environment = 'development') {
    this.environment = environment;
    this.config = null;
    this.lastFetch = null;
    this.cacheTimeout = 5 * 60 * 1000; // 5 minutes
  }

  getDiscoveryUrl() {
    const baseUrl = this.environment === 'production'
      ? 'https://account.oten.com'
      : 'https://account.sbx.oten.dev';

    return `${baseUrl}/.well-known/openid-configuration`;
  }

  async fetchConfiguration() {
    const discoveryUrl = this.getDiscoveryUrl();
    
    try {
      console.log(`Fetching configuration from: ${discoveryUrl}`);
      const response = await fetch(discoveryUrl);
      
      if (!response.ok) {
        throw new Error(`HTTP ${response.status}: ${response.statusText}`);
      }
      
      const config = await response.json();
      this.config = config;
      this.lastFetch = Date.now();
      
      console.log('✅ Configuration loaded successfully');
      return config;
    } catch (error) {
      console.error('❌ Failed to fetch configuration:', error.message);
      throw error;
    }
  }

  async getConfiguration() {
    // Check if we need to fetch or refresh configuration
    if (!this.config || this.shouldRefresh()) {
      await this.fetchConfiguration();
    }
    
    return this.config;
  }

  shouldRefresh() {
    if (!this.lastFetch) return true;
    return (Date.now() - this.lastFetch) > this.cacheTimeout;
  }

  async getEndpoint(type) {
    const config = await this.getConfiguration();
    
    const endpoints = {
      authorization: config.authorization_endpoint,
      token: config.token_endpoint,
      userinfo: config.userinfo_endpoint,
      jwks: config.jwks_uri,
      issuer: config.issuer
    };

    if (!endpoints[type]) {
      throw new Error(`Unknown endpoint type: ${type}`);
    }

    return endpoints[type];
  }

  async getSupportedFeatures() {
    const config = await this.getConfiguration();
    
    return {
      scopes: config.scopes_supported || [],
      responseTypes: config.response_types_supported || [],
      grantTypes: config.grant_types_supported || [],
      codeChallengeMethod: config.code_challenge_methods_supported || [],
      tokenAuthMethods: config.token_endpoint_auth_methods_supported || [],
      claims: config.claims_supported || [],
      idTokenSigningAlgs: config.id_token_signing_alg_values_supported || []
    };
  }
}

// Usage example
const discovery = new OtenDiscovery('development');

// Get specific endpoints
const authEndpoint = await discovery.getEndpoint('authorization');
const tokenEndpoint = await discovery.getEndpoint('token');

// Get supported features
const features = await discovery.getSupportedFeatures();
console.log('Supported scopes:', features.scopes);
console.log('Supported grant types:', features.grantTypes);
```

## Framework-Specific Examples

### Express.js with Passport

```javascript
const passport = require('passport');
const { Strategy: OIDCStrategy } = require('passport-openidconnect');

async function configurePassport() {
  const discovery = new OtenDiscovery(process.env.NODE_ENV);
  const config = await discovery.getConfiguration();

  passport.use('oten', new OIDCStrategy({
    issuer: config.issuer,
    authorizationURL: config.authorization_endpoint,
    tokenURL: config.token_endpoint,
    userInfoURL: config.userinfo_endpoint,
    clientID: process.env.OTEN_CLIENT_ID,
    clientSecret: process.env.OTEN_CLIENT_SECRET,
    callbackURL: process.env.OTEN_REDIRECT_URI,
    scope: ['openid', 'profile', 'email']
  }, (issuer, profile, done) => {
    return done(null, profile);
  }));
}

// Initialize passport with discovery
configurePassport().catch(console.error);
```

### React SPA with OIDC Client

```javascript
import { UserManager } from 'oidc-client-ts';

class OtenAuth {
  constructor() {
    this.userManager = null;
    this.discovery = new OtenDiscovery('development');
  }

  async initialize() {
    const config = await this.discovery.getConfiguration();
    
    this.userManager = new UserManager({
      authority: config.issuer,
      client_id: process.env.REACT_APP_CLIENT_ID,
      redirect_uri: `${window.location.origin}/callback`,
      response_type: 'code',
      scope: 'openid profile email',
      post_logout_redirect_uri: window.location.origin,
      automaticSilentRenew: true,
      silent_redirect_uri: `${window.location.origin}/silent-callback`,
      
      // Use discovered endpoints
      metadata: {
        issuer: config.issuer,
        authorization_endpoint: config.authorization_endpoint,
        token_endpoint: config.token_endpoint,
        userinfo_endpoint: config.userinfo_endpoint,
        jwks_uri: config.jwks_uri,
        end_session_endpoint: config.end_session_endpoint
      }
    });

    return this.userManager;
  }

  async login() {
    if (!this.userManager) {
      await this.initialize();
    }
    return this.userManager.signinRedirect();
  }
}

// Usage
const auth = new OtenAuth();
await auth.initialize();
```

### Python Flask with Authlib

```python
import os
import requests
from authlib.integrations.flask_client import OAuth
from flask import Flask

class OtenDiscovery:
    def __init__(self, environment='development'):
        self.environment = environment
        self.config = None

    def get_discovery_url(self):
        base_url = ('https://account.oten.com'
                   if self.environment == 'production'
                   else 'https://account.sbx.oten.dev')
        return f"{base_url}/.well-known/openid-configuration"
    
    def fetch_configuration(self):
        discovery_url = self.get_discovery_url()
        response = requests.get(discovery_url)
        response.raise_for_status()
        self.config = response.json()
        return self.config
    
    def get_configuration(self):
        if not self.config:
            self.fetch_configuration()
        return self.config

def create_app():
    app = Flask(__name__)
    oauth = OAuth(app)
    
    # Initialize discovery
    discovery = OtenDiscovery(os.environ.get('FLASK_ENV', 'development'))
    config = discovery.get_configuration()

    # Register OAuth client with discovered configuration
    oten = oauth.register(
        name='oten',
        client_id=os.environ.get('OTEN_CLIENT_ID'),
        client_secret=os.environ.get('OTEN_CLIENT_SECRET'),
        server_metadata_url=discovery.get_discovery_url(),
        client_kwargs={
            'scope': 'openid profile email',
            'code_challenge_method': 'S256'
        }
    )

    return app, oten

app, oten = create_app()
```

## Configuration Caching and Refresh

### Smart Caching Strategy

```javascript
class ConfigurationCache {
  constructor(ttl = 5 * 60 * 1000) { // 5 minutes default TTL
    this.cache = new Map();
    this.ttl = ttl;
  }

  set(key, value) {
    this.cache.set(key, {
      value,
      timestamp: Date.now()
    });
  }

  get(key) {
    const item = this.cache.get(key);
    if (!item) return null;

    if (Date.now() - item.timestamp > this.ttl) {
      this.cache.delete(key);
      return null;
    }

    return item.value;
  }

  clear() {
    this.cache.clear();
  }
}

class CachedOtenDiscovery extends OtenDiscovery {
  constructor(environment = 'development', cacheTTL = 5 * 60 * 1000) {
    super(environment);
    this.cache = new ConfigurationCache(cacheTTL);
  }

  async getConfiguration() {
    const cacheKey = `config_${this.environment}`;
    let config = this.cache.get(cacheKey);

    if (!config) {
      config = await this.fetchConfiguration();
      this.cache.set(cacheKey, config);
    }

    return config;
  }

  clearCache() {
    this.cache.clear();
  }
}
```

## Testing Discovery Configuration

### Configuration Validation

```javascript
async function validateDiscoveryConfiguration(environment = 'development') {
  const discovery = new OtenDiscovery(environment);
  
  console.log(`Validating discovery configuration for ${environment}...`);

  try {
    // Fetch configuration
    const config = await discovery.getConfiguration();
    
    // Validate required fields
    const requiredFields = [
      'issuer',
      'authorization_endpoint', 
      'token_endpoint',
      'jwks_uri',
      'scopes_supported',
      'response_types_supported'
    ];

    const missingFields = requiredFields.filter(field => !config[field]);
    
    if (missingFields.length > 0) {
      throw new Error(`Missing required fields: ${missingFields.join(', ')}`);
    }

    // Validate endpoints are accessible
    const endpoints = [
      { name: 'JWKS', url: config.jwks_uri },
      { name: 'Authorization', url: config.authorization_endpoint, method: 'HEAD' }
    ];

    for (const endpoint of endpoints) {
      try {
        const response = await fetch(endpoint.url, { 
          method: endpoint.method || 'GET',
          timeout: 5000 
        });
        
        if (response.ok) {
          console.log(`✅ ${endpoint.name} endpoint accessible`);
        } else {
          console.log(`⚠️ ${endpoint.name} endpoint returned ${response.status}`);
        }
      } catch (error) {
        console.log(`❌ ${endpoint.name} endpoint error: ${error.message}`);
      }
    }

    // Validate supported features
    const features = await discovery.getSupportedFeatures();
    
    console.log('\nDiscovered Configuration:');
    console.log(`Issuer: ${config.issuer}`);
    console.log(`Scopes: ${features.scopes.join(', ')}`);
    console.log(`Response Types: ${features.responseTypes.join(', ')}`);
    console.log(`Grant Types: ${features.grantTypes.join(', ')}`);
    console.log(`PKCE Methods: ${features.codeChallengeMethod.join(', ')}`);

    return true;

  } catch (error) {
    console.error('❌ Discovery validation failed:', error.message);
    return false;
  }
}

// Run validation
validateDiscoveryConfiguration('development')
  .then(success => {
    if (success) {
      console.log('\nDiscovery configuration validation passed!');
    } else {
      console.log('\nDiscovery configuration validation failed!');
    }
  });
```

***

**Next**: [Step 1: Choose Library](broken://pages/CuJNdfiNnGHuqh7gDIK2)
