Skip to content

Programmatic Credentials Support for External Secret Management #1019

@fabiankaegy

Description

@fabiankaegy

Problem Statement

Currently, ClassifAI stores API credentials (API keys, endpoint URLs, etc.) in the WordPress database via options. This prevents integration with external secret management services like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault, where credentials should be fetched at runtime rather than stored in the database.

Proposed Solution

Create a unified credential filtering system that works across all providers, following WordPress conventions. This involves:

  1. A single generalized filter hook that all providers use
  2. A centralized helper class for credential management
  3. Provider-specific secondary filters for fine-grained control

Implementation Details

1. Create a Centralized Credentials Helper Class

Create a new helper class at includes/Classifai/Helpers/Credentials.php:

<?php
namespace Classifai\Helpers;

/**
 * Centralized credential management for all providers.
 *
 * @since 3.x.0
 */
class Credentials {

    /**
     * Get credentials for a provider.
     *
     * @param string $provider_id The provider ID (e.g., 'azure_openai', 'openai_chatgpt').
     * @param string $feature_id  The feature ID (e.g., 'feature_title_generation').
     * @param array  $settings    The provider settings from the database.
     * @return array Filtered credentials array.
     */
    public static function get_credentials( string $provider_id, string $feature_id, array $settings ): array {
        /**
         * Filter provider credentials before making an API request.
         *
         * This is the primary hook for integrating external secret management
         * services like Azure Key Vault, AWS Secrets Manager, or HashiCorp Vault.
         *
         * @since 3.x.0
         * @hook classifai_provider_credentials
         *
         * @param array  $credentials The credentials array from settings.
         * @param string $provider_id The provider ID (e.g., 'azure_openai', 'openai_chatgpt').
         * @param string $feature_id  The feature ID making the request.
         *
         * @return array Filtered credentials array.
         */
        return apply_filters(
            'classifai_provider_credentials',
            $settings,
            $provider_id,
            $feature_id
        );
    }

    /**
     * Get a specific credential value.
     *
     * @param string $provider_id    The provider ID.
     * @param string $feature_id     The feature ID.
     * @param array  $settings       The provider settings.
     * @param string $credential_key The specific credential key (e.g., 'api_key').
     * @return mixed The credential value.
     */
    public static function get_credential( 
        string $provider_id, 
        string $feature_id, 
        array $settings, 
        string $credential_key 
    ) {
        $credentials = self::get_credentials( $provider_id, $feature_id, $settings );
        return $credentials[ $credential_key ] ?? '';
    }
}

2. Update the Base Provider Class

Modify includes/Classifai/Providers/Provider.php to add a method that providers can use:

/**
 * Get filtered credentials for this provider.
 *
 * @param string $feature_id The feature ID making the request.
 * @return array The filtered credentials.
 */
protected function get_provider_credentials( string $feature_id ): array {
    $settings = $this->feature_instance->get_settings( static::ID );
    
    return \Classifai\Helpers\Credentials::get_credentials(
        static::ID,
        $feature_id,
        $settings
    );
}

3. Update All Providers to Use Centralized Credentials

Each provider will be updated to use the centralized credential retrieval. The credential keys vary by provider:

Provider Credential Keys
Azure OpenAI api_key, endpoint_url, deployment
Azure Embeddings api_key, endpoint_url, deployment
Azure Computer Vision api_key, endpoint_url
Azure Speech api_key, endpoint_url
OpenAI (all) api_key
Google AI api_key
IBM Watson api_key, endpoint_url
AWS Polly access_key_id, secret_access_key
ElevenLabs api_key
xAI Grok api_key

4. Files to Modify

New file:

  • includes/Classifai/Helpers/Credentials.php - New centralized helper

Provider files to update:

  • includes/Classifai/Providers/Provider.php - Add base method
  • includes/Classifai/Providers/Azure/OpenAI.php
  • includes/Classifai/Providers/Azure/Embeddings.php
  • includes/Classifai/Providers/Azure/ComputerVision.php
  • includes/Classifai/Providers/Azure/Speech.php
  • includes/Classifai/Providers/Azure/Read.php
  • includes/Classifai/Providers/Azure/SmartCropping.php
  • includes/Classifai/Providers/OpenAI/APIRequest.php
  • includes/Classifai/Providers/GoogleAI/APIRequest.php
  • includes/Classifai/Providers/Watson/NLU.php
  • includes/Classifai/Providers/AWS/AmazonPolly.php
  • includes/Classifai/Providers/ElevenLabs/ElevenLabs.php
  • includes/Classifai/Providers/XAI/APIRequest.php

Usage Examples

Example 1: Azure Key Vault Integration (All Providers)

add_filter( 'classifai_provider_credentials', function( $credentials, $provider_id, $feature_id ) {
    // Only override for Azure OpenAI providers
    if ( ! str_starts_with( $provider_id, 'azure_' ) ) {
        return $credentials;
    }
    
    $vault = new AzureKeyVaultClient( AZURE_VAULT_URL );
    
    return array_merge( $credentials, [
        'api_key' => $vault->get_secret( "classifai-{$provider_id}-api-key" ),
    ]);
}, 10, 3 );

Example 2: Environment Variable Credentials

add_filter( 'classifai_provider_credentials', function( $credentials, $provider_id, $feature_id ) {
    $env_key = strtoupper( "CLASSIFAI_{$provider_id}_API_KEY" );
    
    if ( defined( $env_key ) ) {
        $credentials['api_key'] = constant( $env_key );
    }
    
    return $credentials;
}, 10, 3 );

Example 3: Feature-Specific Credentials

add_filter( 'classifai_provider_credentials', function( $credentials, $provider_id, $feature_id ) {
    // Use different API key for content generation vs title generation
    if ( 'feature_content_generation' === $feature_id ) {
        $credentials['api_key'] = get_premium_api_key();
    }
    
    return $credentials;
}, 10, 3 );

Considerations

  • Backward Compatibility: Existing installations continue to work unchanged since the filter passes original values by default.
  • Caching: Developers should implement caching when using external secret services to avoid excessive API calls.
  • Settings Page: The authentication check on settings pages will also use filtered credentials, so developers can test their integration.
  • Error Handling: Empty credentials will be caught by existing validation and display appropriate error messages.
  • Security: Filtered credentials are never stored back to the database; they're only used for the current request.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

Status

Code Review

Relationships

None yet

Development

No branches or pull requests

Issue actions