OpenID Connect Discovery
Oten IDP provides OpenID Connect Discovery endpoints that automatically provide all configuration information your application needs. This is the recommended way to get configuration details.
Discovery Endpoints
Production Environment
Copy Discovery URL: https://account.oten.com/.well-known/openid-configuration Sandbox Environment
Copy Discovery URL: https://account.sbx.oten.dev/.well-known/openid-configuration How to Use Discovery
Instead of hardcoding endpoints, fetch configuration dynamically:
Copy // Fetch configuration automatically
async function getOIDCConfiguration (environment = 'production' ) {
const discoveryUrl = environment === 'production'
? 'https://account.oten.com/.well-known/openid-configuration'
: 'https://account.sbx.oten.dev/.well-known/openid-configuration' ;
const response = await fetch (discoveryUrl);
const config = await response .json ();
return {
issuer : config .issuer ,
authorizationEndpoint : config .authorization_endpoint ,
tokenEndpoint : config .token_endpoint ,
jwksUri : config .jwks_uri ,
supportedScopes : config .scopes_supported ,
supportedResponseTypes : config .response_types_supported ,
supportedGrantTypes : config .grant_types_supported ,
supportedCodeChallengeMethods : config .code_challenge_methods_supported ,
supportedTokenAuthMethods : config .token_endpoint_auth_methods_supported ,
supportedClaims : config .claims_supported ,
supportedIdTokenSigningAlgs : config .id_token_signing_alg_values_supported
};
}
// Usage example
const config = await getOIDCConfiguration ( 'development' );
console .log ( 'Authorization Endpoint:' , config .authorizationEndpoint);
console .log ( 'Token Endpoint:' , config .tokenEndpoint);
console .log ( 'Supported Scopes:' , config .supportedScopes); The discovery endpoint returns a JSON object with the following structure:
Copy {
"issuer": "https://account.sbx.oten.dev",
"authorization_endpoint": "https://account.sbx.oten.dev/v1/oauth/authorize",
"token_endpoint": "https://account.sbx.oten.dev/v1/oauth/token",
"jwks_uri": "https://account.sbx.oten.dev/.well-known/jwks.json",
"response_types_supported": ["code"],
"subject_types_supported": ["public"],
"id_token_signing_alg_values_supported": ["EdDSA"],
"scopes_supported": ["openid", "profile", "email"],
"token_endpoint_auth_methods_supported": ["client_secret_post"],
"claims_supported": ["sub", "name", "email"],
"code_challenge_methods_supported": ["S256", "plain"],
"grant_types_supported": ["authorization_code", "refresh_token"]
} Configuration Parameters Explained
Parameter
Description
Oten Values
Identity provider identifier
https://account.[env].oten.dev or https://account.oten.com
Public keys for token verification
Supported OAuth response types
id_token_signing_alg_values_supported
ID token signing algorithms
["openid", "profile", "email"]
token_endpoint_auth_methods_supported
Client authentication methods
code_challenge_methods_supported
Supported OAuth grant types
["authorization_code", "refresh_token"]
🌐 Manual Endpoint Configuration
If you prefer to configure endpoints manually (not recommended), here are the static endpoints:
Production Environment
Copy Base Domain: oten.com
Authorization Server: https://account.oten.com
Token Endpoint: https://account.oten.com/v1/oauth/token
Authorization Endpoint: https://account.oten.com/v1/oauth/authorize
Token Validation: https://account.oten.com/v1/token/validate
User Info: https://account.oten.com/v1/userinfo
JWKS URI: https://account.oten.com/.well-known/jwks.json
OIDC Discovery: https://account.oten.com/.well-known/openid-configuration Sandbox Environment
Copy Base Domain: oten.dev
Authorization Server: https://account.sbx.oten.dev
Token Endpoint: https://account.sbx.oten.dev/v1/oauth/token
Authorization Endpoint: https://account.sbx.oten.dev/v1/oauth/authorize
Token Validation: https://account.sbx.oten.dev/v1/token/validate
User Info: https://account.sbx.oten.dev/v1/userinfo
JWKS URI: https://account.sbx.oten.dev/.well-known/jwks.json
OIDC Discovery: https://account.sbx.oten.dev/.well-known/openid-configuration 🛠️ Library-Specific Configuration Examples
Node.js with Passport.js
Copy const passport = require('passport');
const { Strategy: OIDCStrategy } = require('passport-openidconnect');
// Configure using discovery URL
passport.use('oten', new OIDCStrategy({
issuer: 'https://account.sbx.oten.dev',
authorizationURL: 'https://account.sbx.oten.dev/v1/oauth/authorize',
tokenURL: 'https://account.sbx.oten.dev/v1/oauth/token',
userInfoURL: 'https://account.sbx.oten.dev/v1/userinfo',
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);
})); Python with Authlib
Copy from authlib.integrations.flask_client import OAuth
oauth = OAuth(app)
# Configure using discovery URL
oten = oauth.register(
name='oten',
client_id=os.environ.get('OTEN_CLIENT_ID'),
client_secret=os.environ.get('OTEN_CLIENT_SECRET'),
server_metadata_url='https://account.sbx.oten.dev/.well-known/openid-configuration',
client_kwargs={
'scope': 'openid profile email',
'code_challenge_method': 'S256' # Enable PKCE
}
) Java with Spring Security
Copy @Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public ClientRegistrationRepository clientRegistrationRepository() {
return new InMemoryClientRegistrationRepository(otenClientRegistration());
}
private ClientRegistration otenClientRegistration() {
return ClientRegistration.withRegistrationId("oten")
.clientId(System.getenv("OTEN_CLIENT_ID"))
.clientSecret(System.getenv("OTEN_CLIENT_SECRET"))
.clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_POST)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.redirectUri("{baseUrl}/login/oauth2/code/{registrationId}")
.scope("openid", "profile", "email")
.issuerUri("https://account.sbx.oten.dev")
.build();
}
} React SPA with OIDC Client
Copy import { UserManager } from 'oidc-client-ts';
const userManager = new UserManager({
authority: 'https://account.sbx.oten.dev',
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,
// PKCE is automatically enabled
automaticSilentRenew: true,
silent_redirect_uri: `${window.location.origin}/silent-callback`,
// Discovery will automatically fetch endpoints
metadata: {
issuer: 'https://account.sbx.oten.dev',
authorization_endpoint: 'https://account.sbx.oten.dev/v1/oauth/authorize',
token_endpoint: 'https://account.sbx.oten.dev/v1/oauth/token',
userinfo_endpoint: 'https://account.sbx.oten.dev/v1/userinfo',
jwks_uri: 'https://account.sbx.oten.dev/.well-known/jwks.json'
}
}); .NET Core
Copy public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
options.Authority = "https://account.sbx.oten.dev";
options.ClientId = Configuration["Oten:ClientId"];
options.ClientSecret = Configuration["Oten:ClientSecret"];
options.ResponseType = "code";
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.SaveTokens = true;
options.UsePkce = true; // Enable PKCE
});
} 🔄 Dynamic Configuration Loading
Environment-Aware Configuration
Copy class OtenConfig {
constructor(environment = 'development') {
this.environment = environment;
this.config = null;
}
async loadConfiguration() {
const discoveryUrl = this.getDiscoveryUrl();
try {
const response = await fetch(discoveryUrl);
if (!response.ok) {
throw new Error(`Failed to fetch configuration: ${response.status}`);
}
this.config = await response.json();
return this.config;
} catch (error) {
console.error('Failed to load OIDC configuration:', error);
// Fallback to static configuration
return this.getFallbackConfiguration();
}
}
getDiscoveryUrl() {
const baseUrl = this.environment === 'production'
? 'https://account.oten.com'
: 'https://account.sbx.oten.dev';
return `${baseUrl}/.well-known/openid-configuration`;
}
getFallbackConfiguration() {
const baseUrl = this.environment === 'production'
? 'https://account.oten.com'
: 'https://account.sbx.oten.dev';
return {
issuer: baseUrl,
authorization_endpoint: `${baseUrl}/v1/oauth/authorize`,
token_endpoint: `${baseUrl}/v1/oauth/token`,
jwks_uri: `${baseUrl}/.well-known/jwks.json`,
userinfo_endpoint: `${baseUrl}/v1/userinfo`,
scopes_supported: ['openid', 'profile', 'email'],
response_types_supported: ['code'],
grant_types_supported: ['authorization_code', 'refresh_token']
};
}
async getEndpoint(type) {
if (!this.config) {
await this.loadConfiguration();
}
const endpoints = {
authorization: this.config.authorization_endpoint,
token: this.config.token_endpoint,
userinfo: this.config.userinfo_endpoint,
jwks: this.config.jwks_uri
};
return endpoints[type];
}
async getSupportedFeatures() {
if (!this.config) {
await this.loadConfiguration();
}
return {
scopes: this.config.scopes_supported || [],
responseTypes: this.config.response_types_supported || [],
grantTypes: this.config.grant_types_supported || [],
codeChallengeMethod: this.config.code_challenge_methods_supported || [],
tokenAuthMethods: this.config.token_endpoint_auth_methods_supported || [],
claims: this.config.claims_supported || []
};
}
}
// Usage
const config = new OtenConfig('development');
await config.loadConfiguration();
const authEndpoint = await config.getEndpoint('authorization');
const supportedScopes = await config.getSupportedFeatures();
console.log('Available scopes:', supportedScopes.scopes); Support Channels
Technical Support : support@oten.dev
Developer Resources
Developer Portal : https://developer.oten.com (Coming Soon)
API Documentation : https://docs.oten.com (Coming Soon)
Status Page : https://status.oten.com (Coming Soon)
Rate Limits
Production Limits
Authorization endpoint : 10 requests/minute per IP
Token endpoint : 60 requests/minute per client
Validation endpoint : 1000 requests/minute per client
User Info endpoint : 100 requests/minute per token
Development Limits
Authorization endpoint : 20 requests/minute per IP
Token endpoint : 120 requests/minute per client
Validation endpoint : 2000 requests/minute per client
User Info endpoint : 200 requests/minute per token
🔐 Supported Algorithms and Methods
JAR Signing Algorithms
HS256 : HMAC with SHA-256 (uses client_secret)
EdDSA : Edwards-curve Digital Signature with Ed25519
Token Signing Algorithms
EdDSA : Edwards-curve Digital Signature (for ID tokens and access tokens)
PKCE Code Challenge Methods
S256 : SHA256 hash of code verifier (recommended)
plain : Plain text code verifier (not recommended for production)
Client Authentication Methods
client_secret_post : Client credentials in request body (default)
Supported Response Types
code : Authorization code flow (only supported type)
Supported Grant Types
authorization_code : Standard OAuth 2.0 authorization code flow
refresh_token : Token refresh using refresh tokens
Supported Scopes and Claims
Available Scopes
Based on the discovery endpoint, Oten IDP currently supports:
openid : Required for OpenID Connect flow - enables ID token issuance
profile : Access to basic profile information (name, etc.)
email : Access to user's email address
Available Claims
The following claims are available in ID tokens and UserInfo endpoint:
sub : Subject identifier - unique user ID
email : User's email address
Scope-to-Claims Mapping
Scope
Claims Included
Description
Minimum required scope, provides user identifier
Adds user's email address
Example Scope Requests
Copy // Minimum required scope
scope: "openid"
// Basic user information
scope: "openid profile"
// Full user information
scope: "openid profile email" Future Scopes (Coming Soon)
Additional scopes may be added in future releases:
phone: Phone number access
offline_access: Refresh token capability
workspace: Workspace information access
Testing and Validation
Validate Discovery Endpoint
Copy # Test discovery endpoint
curl -s https://account.sbx.oten.dev/.well-known/openid-configuration | jq .
# Check specific configuration values
curl -s https://account.sbx.oten.dev/.well-known/openid-configuration | jq '.scopes_supported' Validate JWKS Endpoint
Copy # Test JWKS endpoint
curl -s https://account.sbx.oten.dev/.well-known/jwks.json | jq . Configuration Validation Script
Copy async function validateOtenConfiguration(environment = 'development') {
const baseUrl = environment === 'production'
? 'https://account.oten.com'
: 'https://account.sbx.oten.dev';
console.log(`Validating Oten IDP configuration for ${environment}...`);
try {
// Test discovery endpoint
const discoveryResponse = await fetch(`${baseUrl}/.well-known/openid-configuration`);
if (!discoveryResponse.ok) {
throw new Error(`Discovery endpoint failed: ${discoveryResponse.status}`);
}
const config = await discoveryResponse.json();
console.log('✅ Discovery endpoint accessible');
// 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(', ')}`);
}
console.log('✅ All required configuration fields present');
// Test JWKS endpoint
const jwksResponse = await fetch(config.jwks_uri);
if (!jwksResponse.ok) {
throw new Error(`JWKS endpoint failed: ${jwksResponse.status}`);
}
const jwks = await jwksResponse.json();
if (!jwks.keys || jwks.keys.length === 0) {
throw new Error('JWKS endpoint has no keys');
}
console.log('✅ JWKS endpoint accessible and has keys');
// Validate supported features
const validations = [
{
name: 'Authorization Code Flow',
check: config.response_types_supported.includes('code'),
message: 'Authorization code flow is supported'
},
{
name: 'PKCE Support',
check: config.code_challenge_methods_supported &&
config.code_challenge_methods_supported.includes('S256'),
message: 'PKCE with S256 is supported'
},
{
name: 'OpenID Connect',
check: config.scopes_supported.includes('openid'),
message: 'OpenID Connect is supported'
},
{
name: 'Client Secret Post',
check: config.token_endpoint_auth_methods_supported.includes('client_secret_post'),
message: 'Client secret post authentication is supported'
}
];
validations.forEach(validation => {
if (validation.check) {
console.log(`✅ ${validation.message}`);
} else {
console.log(`❌ ${validation.name} validation failed`);
}
});
console.log('\nConfiguration Summary:');
console.log(`Issuer: ${config.issuer}`);
console.log(`Authorization Endpoint: ${config.authorization_endpoint}`);
console.log(`Token Endpoint: ${config.token_endpoint}`);
console.log(`Supported Scopes: ${config.scopes_supported.join(', ')}`);
console.log(`Supported Claims: ${config.claims_supported.join(', ')}`);
return config;
} catch (error) {
console.error('❌ Configuration validation failed:', error.message);
throw error;
}
}
// Usage
validateOtenConfiguration('development')
.then(() => console.log('\nConfiguration validation completed successfully'))
.catch(error => console.error('\nConfiguration validation failed:', error.message)); Health Check Script
Copy async function healthCheck(environment = 'development') {
const baseUrl = environment === 'production'
? 'https://account.oten.com'
: 'https://account.sbx.oten.dev';
const endpoints = [
{ name: 'Discovery', url: `${baseUrl}/.well-known/openid-configuration` },
{ name: 'JWKS', url: `${baseUrl}/.well-known/jwks.json` },
{ name: 'Authorization', url: `${baseUrl}/v1/oauth/authorize`, method: 'HEAD' }
];
console.log(`Health check for ${environment} environment...`);
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}: OK (${response.status})`);
} else {
console.log(`⚠️ ${endpoint.name}: ${response.status} ${response.statusText}`);
}
} catch (error) {
console.log(`❌ ${endpoint.name}: ${error.message}`);
}
}
} Error Codes Reference
Complete Reference : See Error Codes Reference for comprehensive error documentation with troubleshooting guides.
Quick Error Reference
Most Common Errors:
Error Code
Endpoint
Description
Action Required
Missing request parameter or JAR expired
Authorization code/refresh token invalid
Client auth failed or IP not whitelisted
Client not authorized for grant type
Authorization Endpoint:
Copy Location: https://yourapp.com/callback?error=invalid_request&error_description=Request%20parameter%20is%20required&state=xyz Token Endpoint:
Copy {
"error": "invalid_grant",
"error_description": "Invalid authorization code: code expired"
} Error Handling Guidelines
Always validate state parameter in authorization callbacks
Implement retry logic for server_error and temporarily_unavailable
Don't retry for invalid_grant, invalid_client, or configuration errors
Log detailed errors server-side for debugging
Provide user-friendly messages based on error codes
Security-Enhanced Error Handling
JAR (JWT-Secured Authorization Request) Requirements:
Either request OR request_uri parameter is REQUIRED
Cannot provide both request and request_uri parameters simultaneously
Missing request parameter results in invalid_request with message "Request parameter is required"
IP Address Validation (Client Credentials Grant):
IP address validation is enforced when whitelist is configured
Requests from non-whitelisted IPs receive invalid_client error
Missing IP address triggers security warnings
Client Type Enforcement:
Client credentials grant restricted to confidential clients only
Public clients receive unauthorized_client error
Client type validation occurs before credential verification
Error Codes Reference : Complete error documentation
Common Errors : Step-by-step troubleshooting
Security Requirements
Mandatory Security Features
PKCE : Required for all public clients, recommended for confidential clients
JAR : Required for confidential clients only, forbidden for public clients
HTTPS : Required for all endpoints (except localhost in development)
State Parameter : Required to prevent CSRF attacks
Token Lifetimes
Authorization Code : 10 minutes
Access Token : 1 hour (configurable)
Refresh Token : 30 days (configurable)
JAR Token : 5 minutes maximum
Client Registration Requirements
Application type (Web App, SPA, Mobile App, Server-to-Server)
Redirect URIs (must be HTTPS except localhost)
JAR signing method (HS256 or EdDSA)
Last updated 4 months ago