Configure Orchestrated Discovery
Orchestrated Discovery uses custom JavaScript logic to determine provider routing. Use this mode when you need advanced routing decisions beyond simple domain matching or user lookups.
When to use Orchestrated Discovery
Use this mode when you need:
- Multi-layer routing based on multiple factors
- Geographic or device-based provider selection
- Risk-based authentication routing
- Dynamic sub-organization selection
- Complex business logic for provider assignment
Prerequisites
- Access to SecureAuth Connect
- JavaScript knowledge and experience writing logic
- At least one identity provider configured in your workspace
- Understanding of the execution context available to scripts
How Orchestrated Discovery works
- User enters their identifier
- SecureAuth Connect triggers the
identifier_idp_selectionscript - Your custom JavaScript evaluates available context (geographic location, device, etc.)
- Script returns a provider ID or null
- If a provider ID is returned, user is routed to that provider
- If null is returned, system falls back to automatic/default behavior
Enable Orchestrated Discovery
- In SecureAuth Connect, go to Authentication > Providers
- Click the Discovery tab
- Select the Orchestrated Discovery option
- This enables the custom scripting execution point for provider selection
Create your discovery script
Step 1: Access the Scripts interface
- Go to Extensions > Scripts
- Click Create Script or edit an existing script
- Choose the Execution Point as
identifier_idp_selection
Step 2: Write your custom logic
Your script runs when a user enters their identifier during login. It receives context about the login attempt and must return a provider ID or null.
Script input context
Your script receives the following information:
{
identifier, // User-entered identifier (email or username)
client_id, // OAuth client ID of the application initiating login
request_url, // Full URL that initiated the login
idps, // Array of available identity providers
orgs, // Array of available organizations in the workspace
headers, // HTTP headers from the login request
geo, // Geographic information (country, region, city, etc.)
device // Device information and fingerprinting data
}
Script return value
Your script must return one of:
// Route to a specific provider
{ idp_id: "provider-id" }
// Fall back to automatic discovery (Intelligent Discovery rules or default behavior)
{ idp_id: null }
Script examples
Example 1: Domain-based routing
Route users based on email domain.
// Route company employees to Azure AD
if (identifier.endsWith("@company.com")) {
return { idp_id: "azure-ad-provider-id" };
}
// Route Gmail users to Google
if (identifier.endsWith("@gmail.com")) {
return { idp_id: "google-provider-id" };
}
// Route iCloud users to Apple
if (identifier.endsWith("@icloud.com")) {
return { idp_id: "apple-provider-id" };
}
// No match found - use fallback or default
return { idp_id: null };
Example 2: Geographic routing
Route users based on their geographic location.
// Route US users to US-based provider
if (geo && geo.country === "US") {
return { idp_id: "us-provider-id" };
}
// Route EU users to EU-based provider
if (geo && geo.country && ["DE", "FR", "GB", "IT", "ES"].includes(geo.country)) {
return { idp_id: "eu-provider-id" };
}
// Route APAC users to Asia-Pacific provider
if (geo && geo.country && ["AU", "JP", "SG", "IN"].includes(geo.country)) {
return { idp_id: "apac-provider-id" };
}
return { idp_id: null };
Example 3: Multi-tenant routing
Route users to their organization's provider based on user directory lookup.
// Look up user in available organizations
const userOrg = orgs.find(org => {
// Check if this organization has users
if (!org.users) return false;
// Check if the user is in this organization
return org.users.includes(identifier);
});
// If user found in an organization, route to that org's provider
if (userOrg && userOrg.idp_id) {
return { idp_id: userOrg.idp_id };
}
// User not found in any organization - use fallback
return { idp_id: null };
Example 4: Risk-based routing
Route users to different providers based on device trust.
// If device is trusted, route to standard provider
if (device && device.is_trusted) {
return { idp_id: "standard-provider-id" };
}
// If device is not trusted, route to MFA-enabled provider
if (device && !device.is_trusted) {
return { idp_id: "mfa-provider-id" };
}
// Unknown device status - use default
return { idp_id: null };
Example 5: Combined logic
Route based on multiple factors (domain + geography + device).
// Extract domain from identifier
const domain = identifier.split("@")[1];
// Check company domain with geographic preference
if (domain === "company.com") {
// Route US employees to US provider
if (geo && geo.country === "US") {
return { idp_id: "azure-ad-us-id" };
}
// Route EU employees to EU provider
if (geo && geo.country && ["DE", "FR", "GB"].includes(geo.country)) {
return { idp_id: "azure-ad-eu-id" };
}
// Default company provider
return { idp_id: "azure-ad-default-id" };
}
// Route external users by domain
if (domain === "gmail.com") {
return { idp_id: "google-provider-id" };
}
// Fall back to default behavior for unmatched identifiers
return { idp_id: null };
Best practices
Script development
- Start simple - Begin with basic logic and add complexity gradually
- Use console.log() - Log values to debug your script in logs
- Test thoroughly - Test with various user identifiers and scenarios
- Handle edge cases - Account for null values and unexpected inputs
- Comment your code - Document logic for future maintenance
Performance
- Keep scripts efficient - Avoid heavy computation or external API calls
- Minimize database lookups - Cache data when possible
- Set timeouts - Long-running scripts can slow down login
Security
- Validate inputs - Check identifier format before using
- Avoid exposing data - Don't log or return sensitive information
- Use specific matching - Avoid overly broad patterns that might match unintended users
- Test security implications - Ensure script doesn't inadvertently expose user information
Testing your script
Save and activate your script
- Write or update your script in the Scripts interface
- Click Save
- Ensure the script is active/enabled
Test with the admin console
- Go to Authentication > Providers > Discovery tab
- Verify Orchestrated Discovery is selected
- Test with different identifiers to verify routing behavior
Check logs for debugging
- Go to Extensions > Scripts
- View execution logs to see:
- Script execution status (success/error)
- Return values
- console.log() output
- Any errors or exceptions
Test scenarios
Test your script with various user identifiers:
- Identifiers from different domains
- Users from different geographic locations (if using geo routing)
- Edge cases and unusual formats
- Invalid or non-existent identifiers
- Multiple matches (if your logic allows)
Troubleshooting script errors
Script returns syntax error:
- Check JavaScript syntax in your script
- Verify all functions and variables are correctly spelled
- Use a JavaScript linter if available
Script doesn't find expected provider:
- Log the available providers:
console.log(idps); - Verify the provider ID you're returning matches an actual provider
- Check provider IDs in the UI under Authentication > Providers
Script runs but doesn't route correctly:
- Add console.log() statements to debug values
- Check that your conditional logic is correct
- Verify the returned provider ID format:
{ idp_id: "value" }
Performance is slow:
- Review script for expensive operations
- Optimize loops and conditionals
- Consider caching computed values
Advanced patterns
Combine with Intelligent Discovery
You can use Orchestrated Discovery alongside Intelligent Discovery by returning { idp_id: null } to fall back to Intelligent Discovery rules:
// Custom logic for specific cases
if (identifier === "admin@company.com") {
return { idp_id: "admin-provider-id" };
}
// Fall back to Intelligent Discovery rules for other users
return { idp_id: null };
Dynamic sub-organization selection
For B2B workspaces with multiple customer organizations:
// Extract organization identifier from email (before @)
const [user, domain] = identifier.split("@");
// Map subdomains to organization IDs
const orgMapping = {
"customer-a": "customer-a-org-id",
"customer-b": "customer-b-org-id",
};
// Look up org from identifier prefix
const orgId = orgMapping[user];
if (orgId) {
// Find the provider for this organization
const org = orgs.find(o => o.id === orgId);
if (org && org.idp_id) {
return { idp_id: org.idp_id };
}
}
return { idp_id: null };
Next steps
- To use simpler automatic routing, see Configure Intelligent Discovery
- For more information about discovery modes, see Understanding Identity Provider Discovery
- For provider-specific setup, see Supported providers