/**
* Function to create a custom OIDC application in Okta.
*
* @param {Object} args - Arguments for creating the application.
* @param {string} args.label - The label for the application.
* @param {string} args.applicationType - The application type: 'native', 'web', 'browser' (SPA), or 'service'.
* @param {string[]} [args.redirectUris] - Redirect URIs (required for native, web, and SPA apps).
* @param {string[]} [args.postLogoutRedirectUris] - Post logout redirect URIs (optional for web and SPA apps).
* @param {string[]} [args.grantTypes] - Grant types (auto-configured based on app type if not provided).
* @param {string[]} [args.responseTypes] - Response types (auto-configured based on app type if not provided).
* @param {string} [args.tokenEndpointAuthMethod] - Token endpoint auth method (auto-configured if not provided).
* @param {boolean} [args.pkceRequired] - Whether PKCE is required (auto-configured based on app type if not provided).
* @param {Object} [args.additionalSettings] - Additional application settings.
* @returns {Promise<Object>} - The result of the application creation.
*/
const executeFunction = async ({
name,
label,
signOnMode,
applicationType = 'service', // Default to service if not provided
redirectUris = [],
postLogoutRedirectUris = [],
redirectUri, // Single redirect URI as fallback
postLogoutRedirectUri, // Single post logout redirect URI as fallback
grantTypes,
responseTypes,
tokenEndpointAuthMethod,
pkceRequired,
additionalSettings = {}
}) => {
// Debug logging for received parameters
console.log('=== CREATE APPLICATION DEBUG ===');
console.log('Received parameters:');
console.log('name:', name, typeof name);
console.log('label:', label, typeof label);
console.log('signOnMode:', signOnMode, typeof signOnMode);
console.log('applicationType:', applicationType, typeof applicationType);
console.log('redirectUris:', redirectUris, typeof redirectUris);
console.log('postLogoutRedirectUris:', postLogoutRedirectUris, typeof postLogoutRedirectUris);
console.log('redirectUri:', redirectUri, typeof redirectUri);
console.log('postLogoutRedirectUri:', postLogoutRedirectUri, typeof postLogoutRedirectUri);
console.log('grantTypes:', grantTypes, typeof grantTypes);
console.log('responseTypes:', responseTypes, typeof responseTypes);
console.log('================================');
// Handle array parameters - ensure they're properly formatted
if (redirectUris && !Array.isArray(redirectUris)) {
if (typeof redirectUris === 'string') {
try {
redirectUris = JSON.parse(redirectUris);
console.log('Parsed redirectUris:', redirectUris);
} catch (e) {
console.error('Failed to parse redirectUris, using redirectUri fallback:', e);
redirectUris = redirectUri ? [redirectUri] : [];
}
} else {
redirectUris = redirectUri ? [redirectUri] : [];
}
} else if (!redirectUris && redirectUri) {
redirectUris = [redirectUri];
} else if (!redirectUris) {
redirectUris = [];
}
if (postLogoutRedirectUris && !Array.isArray(postLogoutRedirectUris)) {
if (typeof postLogoutRedirectUris === 'string') {
try {
postLogoutRedirectUris = JSON.parse(postLogoutRedirectUris);
console.log('Parsed postLogoutRedirectUris:', postLogoutRedirectUris);
} catch (e) {
console.error('Failed to parse postLogoutRedirectUris, using postLogoutRedirectUri fallback:', e);
postLogoutRedirectUris = postLogoutRedirectUri ? [postLogoutRedirectUri] : [];
}
} else {
postLogoutRedirectUris = postLogoutRedirectUri ? [postLogoutRedirectUri] : [];
}
} else if (!postLogoutRedirectUris && postLogoutRedirectUri) {
postLogoutRedirectUris = [postLogoutRedirectUri];
} else if (!postLogoutRedirectUris) {
postLogoutRedirectUris = [];
}
console.log('Final redirectUris:', redirectUris);
console.log('Final postLogoutRedirectUris:', postLogoutRedirectUris);
// Import credentials helper
const { getOktaCredentials } = await import('../../lib/tools.js');
const { domain, apiToken } = await getOktaCredentials();
const baseUrl = `https://${domain}`;
// Validate application type
const validAppTypes = ['native', 'web', 'spa', 'service'];
if (!validAppTypes.includes(applicationType)) {
throw new Error(`Invalid application type. Must be one of: ${validAppTypes.join(', ')}`);
}
// Validate redirect URIs for non-service apps
if (applicationType !== 'service' && (!redirectUris || redirectUris.length === 0)) {
throw new Error(`Redirect URIs are required for ${applicationType} applications`);
}
// Auto-configure settings based on application type
let defaultGrantTypes, defaultResponseTypes, defaultTokenAuthMethod, defaultPkceRequired;
switch (applicationType) {
case 'native':
defaultGrantTypes = ['authorization_code', 'refresh_token'];
defaultResponseTypes = ['code'];
defaultTokenAuthMethod = 'client_secret_basic'; // Same as web
defaultPkceRequired = false; // Same as web
break;
case 'web':
defaultGrantTypes = ['authorization_code', 'refresh_token'];
defaultResponseTypes = ['code'];
defaultTokenAuthMethod = 'client_secret_basic';
defaultPkceRequired = false;
break;
case 'spa': // Single Page Application
defaultGrantTypes = ['authorization_code', 'refresh_token', 'interaction_code'];
defaultResponseTypes = ['code'];
defaultTokenAuthMethod = 'none'; // Public client
defaultPkceRequired = true;
break;
case 'service':
defaultGrantTypes = ['client_credentials'];
defaultResponseTypes = ['token'];
defaultTokenAuthMethod = 'client_secret_basic';
defaultPkceRequired = false;
break;
}
// Use provided values or defaults
const finalGrantTypes = grantTypes || defaultGrantTypes;
const finalResponseTypes = responseTypes || defaultResponseTypes;
const finalTokenAuthMethod = tokenEndpointAuthMethod || defaultTokenAuthMethod;
const finalPkceRequired = pkceRequired !== undefined ? pkceRequired : defaultPkceRequired;
// Map application type to Okta's expected values
const oktaApplicationType = applicationType === 'spa' ? 'browser' : applicationType;
// Build the request body - use different structure for SPA apps
let requestBody;
if (applicationType === 'spa') {
// Special handling for SPA applications - include credentials in initial POST
requestBody = {
name: 'oidc_client',
label: label,
signOnMode: 'OPENID_CONNECT',
credentials: {
oauthClient: {
token_endpoint_auth_method: finalTokenAuthMethod,
pkce_required: finalPkceRequired
}
},
settings: {
oauthClient: {
client_uri: null,
logo_uri: null,
redirect_uris: redirectUris,
response_types: finalResponseTypes,
grant_types: finalGrantTypes,
application_type: oktaApplicationType,
consent_method: 'REQUIRED',
issuer_mode: 'DYNAMIC'
}
}
};
// Add optional SPA fields
if (postLogoutRedirectUris && postLogoutRedirectUris.length > 0) {
requestBody.settings.oauthClient.post_logout_redirect_uris = postLogoutRedirectUris;
}
} else if (applicationType === 'web') {
// Special handling for web applications - include credentials in initial POST
requestBody = {
name: 'oidc_client',
label: label,
signOnMode: 'OPENID_CONNECT',
credentials: {
oauthClient: {
token_endpoint_auth_method: finalTokenAuthMethod,
pkce_required: finalPkceRequired
}
},
settings: {
oauthClient: {
client_uri: null,
logo_uri: null,
redirect_uris: redirectUris,
response_types: finalResponseTypes,
grant_types: finalGrantTypes,
application_type: oktaApplicationType,
consent_method: 'REQUIRED',
issuer_mode: 'DYNAMIC'
}
}
};
// Add optional web fields
if (postLogoutRedirectUris && postLogoutRedirectUris.length > 0) {
requestBody.settings.oauthClient.post_logout_redirect_uris = postLogoutRedirectUris;
}
} else if (applicationType === 'native') {
// Special handling for native applications - include credentials in initial POST
requestBody = {
name: 'oidc_client',
label: label,
signOnMode: 'OPENID_CONNECT',
credentials: {
oauthClient: {
token_endpoint_auth_method: finalTokenAuthMethod,
pkce_required: finalPkceRequired
}
},
settings: {
oauthClient: {
client_uri: null,
logo_uri: null,
redirect_uris: redirectUris,
response_types: finalResponseTypes,
grant_types: finalGrantTypes,
application_type: oktaApplicationType,
consent_method: 'REQUIRED',
issuer_mode: 'DYNAMIC'
}
}
};
// Add optional native fields
if (postLogoutRedirectUris && postLogoutRedirectUris.length > 0) {
requestBody.settings.oauthClient.post_logout_redirect_uris = postLogoutRedirectUris;
}
} else {
// Standard structure for service apps
requestBody = {
name: 'oidc_client',
label: label,
signOnMode: 'OPENID_CONNECT',
settings: {
oauthClient: {
application_type: oktaApplicationType,
grant_types: finalGrantTypes,
response_types: finalResponseTypes,
...additionalSettings
}
}
};
}
// Debug logging
console.log('Creating application with request body:', JSON.stringify(requestBody, null, 2));
try {
const response = await fetch(`${baseUrl}/api/v1/apps`, {
method: 'POST',
headers: {
'Authorization': `SSWS ${apiToken}`,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
body: JSON.stringify(requestBody)
});
if (!response.ok) {
const errorText = await response.text();
console.error('Okta API Error:', errorText);
console.error('Request body that was sent:', JSON.stringify(requestBody, null, 2));
let errorData;
try {
errorData = JSON.parse(errorText);
} catch {
errorData = { error: errorText };
}
throw new Error(`HTTP ${response.status}: ${JSON.stringify(errorData)} | Request body: ${JSON.stringify(requestBody)}`);
}
const data = await response.json();
console.log('✅ Application created successfully with credentials in initial POST');
console.log('Final credentials:', JSON.stringify(data.credentials?.oauthClient, null, 2));
// Return useful information about the created app
return {
id: data.id,
label: data.label,
name: data.name,
status: data.status,
signOnMode: data.signOnMode,
applicationType: data.settings?.oauthClient?.application_type,
clientId: data.credentials?.oauthClient?.client_id,
clientSecret: data.credentials?.oauthClient?.client_secret,
grantTypes: data.settings?.oauthClient?.grant_types,
responseTypes: data.settings?.oauthClient?.response_types,
redirectUris: data.settings?.oauthClient?.redirect_uris,
postLogoutRedirectUris: data.settings?.oauthClient?.post_logout_redirect_uris,
tokenEndpointAuthMethod: data.credentials?.oauthClient?.token_endpoint_auth_method,
pkceRequired: data.credentials?.oauthClient?.pkce_required,
created: data.created,
_links: data._links
};
} catch (error) {
console.error('Error creating OIDC application:', error);
return { error: `An error occurred while creating the OIDC application: ${error.message}` };
}
};
/**
* Tool configuration for creating a custom OIDC application in Okta.
* @type {Object}
*/
const apiTool = {
function: executeFunction,
definition: {
type: 'function',
function: {
name: 'create_application',
description: 'Create a custom OIDC application in Okta. Supports native, web, spa (Single Page Application), and service applications with client credentials.',
parameters: {
type: 'object',
properties: {
name: {
type: 'string',
description: 'The name of the application (typically "oidc_client" for OIDC apps).'
},
label: {
type: 'string',
description: 'The display label for the application.'
},
signOnMode: {
type: 'string',
description: 'The sign-on mode for the application (typically "OPENID_CONNECT").'
},
applicationType: {
type: 'string',
enum: ['native', 'web', 'spa', 'service'],
description: 'The application type: native (mobile), web (server-side), spa (Single Page Application), or service (M2M with client credentials).'
},
redirectUris: {
type: 'array',
items: { type: 'string' },
description: 'Redirect URIs for the application. Required for native, web, and spa apps. Not used for service apps.'
},
postLogoutRedirectUris: {
type: 'array',
items: { type: 'string' },
description: 'Post logout redirect URIs. Optional for web and spa apps.'
},
redirectUri: {
type: 'string',
description: 'Single redirect URI as fallback for redirectUris.'
},
postLogoutRedirectUri: {
type: 'string',
description: 'Single post logout redirect URI as fallback for postLogoutRedirectUris.'
},
grantTypes: {
type: 'array',
items: {
type: 'string',
enum: ['authorization_code', 'refresh_token', 'client_credentials', 'implicit', 'password']
},
description: 'OAuth grant types. Auto-configured based on app type if not provided.'
},
responseTypes: {
type: 'array',
items: {
type: 'string',
enum: ['code', 'token', 'id_token']
},
description: 'OAuth response types. Auto-configured based on app type if not provided.'
},
tokenEndpointAuthMethod: {
type: 'string',
enum: ['client_secret_basic', 'client_secret_post', 'client_secret_jwt', 'private_key_jwt', 'none'],
description: 'Token endpoint authentication method. Auto-configured based on app type if not provided.'
},
pkceRequired: {
type: 'boolean',
description: 'Whether PKCE is required. Auto-configured based on app type if not provided (true for native and spa, false for web and service).'
},
additionalSettings: {
type: 'object',
description: 'Additional OAuth client settings as key-value pairs.'
}
},
required: ['name', 'label', 'signOnMode']
}
}
}
};
export { apiTool };