> ## Documentation Index
> Fetch the complete documentation index at: https://withseismic.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Dynamic Credentials & NPM Module Loading

> Implement dynamic authentication systems and runtime module loading for flexible n8n node development

## Dynamic Credentials in n8n

<Info>
  Dynamic credentials allow your nodes to adapt authentication methods at runtime, support multiple auth providers, and integrate with enterprise systems without hardcoding credentials.
</Info>

Traditional n8n nodes have static credential types. Dynamic credentials enable:

* Multi-tenant authentication systems
* Runtime OAuth provider selection
* Credential delegation and impersonation
* Vault integration for secret management
* Just-in-time credential provisioning

## Building Dynamic Credential Systems

### Basic Dynamic Credential Node

```typescript theme={null}
import {
  ICredentialType,
  INodeProperties,
  INodeExecutionData,
  IExecuteFunctions,
  ICredentialDataDecryptedObject,
} from 'n8n-workflow';

export class DynamicAuthNode {
  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    // Get auth method selected by user
    const authMethod = this.getNodeParameter('authMethod', 0) as string;

    // Dynamically load credentials based on selection
    const credentials = await this.loadDynamicCredentials(authMethod);

    // Use credentials to make API call
    const result = await this.makeAuthenticatedRequest(credentials);

    return [[{ json: result }]];
  }

  private async loadDynamicCredentials(
    authMethod: string
  ): Promise<ICredentialDataDecryptedObject> {
    switch (authMethod) {
      case 'oauth2':
        return await this.getCredentials('oAuth2Api');

      case 'apiKey':
        return await this.getCredentials('apiKeyAuth');

      case 'vault':
        return await this.loadFromVault();

      case 'delegated':
        return await this.getDelegatedCredentials();

      default:
        throw new Error(`Unknown auth method: ${authMethod}`);
    }
  }

  private async loadFromVault(): Promise<ICredentialDataDecryptedObject> {
    // Connect to HashiCorp Vault or AWS Secrets Manager
    const vaultEndpoint = this.getNodeParameter('vaultEndpoint', 0) as string;
    const vaultToken = process.env.VAULT_TOKEN;

    const vault = new Vault({
      endpoint: vaultEndpoint,
      token: vaultToken
    });

    const secret = await vault.read('secret/data/api-credentials');

    return {
      apiKey: secret.data.api_key,
      apiSecret: secret.data.api_secret,
      environment: secret.data.environment
    };
  }
}
```

### Multi-Provider OAuth Implementation

<Warning>
  When implementing OAuth, always validate redirect URIs and use state parameters to prevent CSRF attacks. Store refresh tokens securely and implement token rotation.
</Warning>

```typescript theme={null}
export class MultiProviderOAuth implements ICredentialType {
  name = 'multiProviderOAuth';
  displayName = 'Multi-Provider OAuth';
  documentationUrl = 'oauth';

  properties: INodeProperties[] = [
    {
      displayName: 'OAuth Provider',
      name: 'provider',
      type: 'options',
      options: [
        { name: 'Google', value: 'google' },
        { name: 'Microsoft', value: 'microsoft' },
        { name: 'Custom', value: 'custom' }
      ],
      default: 'google',
    },
    {
      displayName: 'Authorization URL',
      name: 'authUrl',
      type: 'string',
      displayOptions: {
        show: {
          provider: ['custom'],
        },
      },
      default: '',
    },
  ];

  async authenticate(
    credentials: ICredentialDataDecryptedObject,
    requestOptions: any
  ): Promise<any> {
    const provider = credentials.provider as string;

    // Load provider-specific configuration
    const config = await this.getProviderConfig(provider, credentials);

    // Build OAuth flow dynamically
    const oauth = new OAuth2(
      config.clientId,
      config.clientSecret,
      config.authUrl,
      config.tokenUrl,
      config.scopes
    );

    // Handle token refresh automatically
    if (this.isTokenExpired(credentials.expiresAt)) {
      const newTokens = await oauth.refreshAccessToken(credentials.refreshToken);
      await this.updateStoredCredentials(newTokens);
      requestOptions.headers.Authorization = `Bearer ${newTokens.accessToken}`;
    } else {
      requestOptions.headers.Authorization = `Bearer ${credentials.accessToken}`;
    }

    return requestOptions;
  }

  private async getProviderConfig(
    provider: string,
    credentials: any
  ): Promise<OAuthConfig> {
    const configs: { [key: string]: OAuthConfig } = {
      google: {
        authUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
        tokenUrl: 'https://oauth2.googleapis.com/token',
        scopes: ['https://www.googleapis.com/auth/drive'],
        clientId: credentials.clientId,
        clientSecret: credentials.clientSecret,
      },
      microsoft: {
        authUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize',
        tokenUrl: 'https://login.microsoftonline.com/common/oauth2/v2.0/token',
        scopes: ['https://graph.microsoft.com/.default'],
        clientId: credentials.clientId,
        clientSecret: credentials.clientSecret,
      },
      custom: {
        authUrl: credentials.authUrl,
        tokenUrl: credentials.tokenUrl,
        scopes: credentials.scopes?.split(',') || [],
        clientId: credentials.clientId,
        clientSecret: credentials.clientSecret,
      },
    };

    return configs[provider] || configs.custom;
  }
}
```

## Dynamic NPM Module Loading

### Runtime Module Installation

<Info>
  Sometimes you need to load NPM modules that aren't bundled with n8n. Dynamic loading allows users to specify packages that your node will install and use at runtime.
</Info>

```typescript theme={null}
import { execSync } from 'child_process';
import * as path from 'path';
import * as fs from 'fs';

export class DynamicModuleLoader {
  private moduleCache = new Map<string, any>();
  private modulePath = path.join(process.cwd(), 'dynamic_modules');

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const moduleName = this.getNodeParameter('npmModule', 0) as string;
    const moduleVersion = this.getNodeParameter('moduleVersion', 0, 'latest') as string;
    const functionName = this.getNodeParameter('functionName', 0) as string;

    // Load module dynamically
    const module = await this.loadModule(moduleName, moduleVersion);

    // Execute specified function
    const result = await this.executeModuleFunction(module, functionName);

    return [[{ json: { result } }]];
  }

  private async loadModule(name: string, version: string): Promise<any> {
    const cacheKey = `${name}@${version}`;

    // Check cache first
    if (this.moduleCache.has(cacheKey)) {
      return this.moduleCache.get(cacheKey);
    }

    // Check if module is already installed
    const modulePath = path.join(this.modulePath, 'node_modules', name);
    if (!fs.existsSync(modulePath)) {
      await this.installModule(name, version);
    }

    // Load the module
    const module = await this.requireModule(modulePath);

    // Cache for future use
    this.moduleCache.set(cacheKey, module);

    return module;
  }

  private async installModule(name: string, version: string): Promise<void> {
    const packageSpec = `${name}@${version}`;

    // Validate module name to prevent injection
    if (!this.isValidModuleName(name)) {
      throw new Error(`Invalid module name: ${name}`);
    }

    // Check against allowlist
    if (!this.isAllowedModule(name)) {
      throw new Error(`Module not in allowlist: ${name}`);
    }

    // Install in isolated directory
    try {
      execSync(`npm install ${packageSpec} --prefix ${this.modulePath}`, {
        timeout: 30000, // 30 second timeout
        stdio: 'pipe'
      });
    } catch (error) {
      throw new Error(`Failed to install module ${packageSpec}: ${error.message}`);
    }
  }

  private async requireModule(modulePath: string): Promise<any> {
    try {
      // Clear require cache for hot-reloading
      delete require.cache[require.resolve(modulePath)];

      // Load module with sandboxing
      const module = require(modulePath);

      // Wrap in proxy for security
      return this.createSecureProxy(module);
    } catch (error) {
      throw new Error(`Failed to load module: ${error.message}`);
    }
  }

  private createSecureProxy(module: any): any {
    return new Proxy(module, {
      get: (target, prop) => {
        // Block dangerous properties
        const blockedProps = ['exec', 'execSync', 'spawn', 'spawnSync'];
        if (blockedProps.includes(String(prop))) {
          throw new Error(`Access to ${String(prop)} is blocked`);
        }
        return target[prop];
      }
    });
  }

  private isValidModuleName(name: string): boolean {
    // Prevent path traversal and command injection
    const validPattern = /^[@a-z0-9][\w.-]*$/i;
    return validPattern.test(name) && !name.includes('/') && !name.includes('\\');
  }

  private isAllowedModule(name: string): boolean {
    const allowlist = [
      'lodash',
      'moment',
      'axios',
      'cheerio',
      'csv-parser',
      'xml2js',
      // Add more safe modules
    ];

    return allowlist.includes(name);
  }
}
```

### Secure Module Sandboxing

```typescript theme={null}
import * as vm from 'vm';

export class SandboxedModuleExecutor {
  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const code = this.getNodeParameter('code', 0) as string;
    const modules = this.getNodeParameter('modules', 0) as string[];

    // Create sandboxed environment
    const result = await this.runInSandbox(code, modules);

    return [[{ json: result }]];
  }

  private async runInSandbox(
    code: string,
    allowedModules: string[]
  ): Promise<any> {
    // Create context with limited globals
    const sandbox = {
      console: {
        log: (...args: any[]) => this.logger.info(...args),
        error: (...args: any[]) => this.logger.error(...args),
      },
      setTimeout: undefined, // Block timers
      setInterval: undefined,
      setImmediate: undefined,
      process: {
        env: {}, // Empty environment
        version: process.version,
      },
      require: this.createSecureRequire(allowedModules),
      result: null,
    };

    // Create VM context
    const context = vm.createContext(sandbox);

    // Wrap code in async function
    const wrappedCode = `
      (async () => {
        ${code}
      })().then(r => result = r).catch(e => result = { error: e.message });
    `;

    try {
      // Run with timeout
      const script = new vm.Script(wrappedCode);
      await script.runInContext(context, {
        timeout: 5000, // 5 second timeout
        breakOnSigint: true,
      });

      // Wait for async completion
      await new Promise(resolve => setTimeout(resolve, 100));

      return sandbox.result;
    } catch (error) {
      throw new Error(`Sandbox execution failed: ${error.message}`);
    }
  }

  private createSecureRequire(allowedModules: string[]) {
    return (moduleName: string) => {
      if (!allowedModules.includes(moduleName)) {
        throw new Error(`Module ${moduleName} is not allowed`);
      }

      try {
        return require(moduleName);
      } catch (error) {
        throw new Error(`Failed to load module ${moduleName}`);
      }
    };
  }
}
```

## Enterprise Credential Management

### Credential Rotation System

```typescript theme={null}
export class CredentialRotationNode {
  private rotationSchedule = new Map<string, Date>();

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const credentialId = this.getNodeParameter('credentialId', 0) as string;
    const rotationPolicy = this.getNodeParameter('rotationPolicy', 0) as any;

    // Check if rotation is needed
    if (await this.needsRotation(credentialId, rotationPolicy)) {
      await this.rotateCredentials(credentialId);
    }

    // Use current credentials
    const credentials = await this.getCurrentCredentials(credentialId);
    return await this.executeWithCredentials(credentials);
  }

  private async needsRotation(
    credentialId: string,
    policy: any
  ): Promise<boolean> {
    const lastRotation = this.rotationSchedule.get(credentialId);

    if (!lastRotation) {
      return true; // Never rotated
    }

    const daysSinceRotation =
      (Date.now() - lastRotation.getTime()) / (1000 * 60 * 60 * 24);

    return daysSinceRotation >= policy.rotationDays;
  }

  private async rotateCredentials(credentialId: string): Promise<void> {
    try {
      // Generate new credentials
      const newCredentials = await this.generateNewCredentials();

      // Update in parallel: new active, old as backup
      await Promise.all([
        this.updateActiveCredentials(credentialId, newCredentials),
        this.archiveOldCredentials(credentialId),
      ]);

      // Update rotation schedule
      this.rotationSchedule.set(credentialId, new Date());

      // Notify administrators
      await this.notifyRotation(credentialId);
    } catch (error) {
      this.logger.error('Credential rotation failed:', error);
      throw error;
    }
  }

  private async generateNewCredentials(): Promise<any> {
    // Generate cryptographically secure credentials
    const crypto = require('crypto');

    return {
      apiKey: crypto.randomBytes(32).toString('hex'),
      apiSecret: crypto.randomBytes(64).toString('hex'),
      generatedAt: new Date().toISOString(),
      expiresAt: new Date(Date.now() + 90 * 24 * 60 * 60 * 1000).toISOString(),
    };
  }
}
```

### Delegation and Impersonation

```typescript theme={null}
export class DelegatedAccessNode {
  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const delegationType = this.getNodeParameter('delegationType', 0) as string;
    const targetUser = this.getNodeParameter('targetUser', 0) as string;

    // Get delegation token
    const delegationToken = await this.getDelegationToken(
      delegationType,
      targetUser
    );

    // Execute with delegated permissions
    return await this.executeAsDelegatedUser(delegationToken);
  }

  private async getDelegationToken(
    type: string,
    targetUser: string
  ): Promise<string> {
    switch (type) {
      case 'serviceAccount':
        return await this.getServiceAccountToken(targetUser);

      case 'impersonation':
        return await this.getImpersonationToken(targetUser);

      case 'oauth2Delegation':
        return await this.getOAuth2DelegationToken(targetUser);

      default:
        throw new Error(`Unknown delegation type: ${type}`);
    }
  }

  private async getServiceAccountToken(user: string): Promise<string> {
    // Google Service Account delegation example
    const { JWT } = require('google-auth-library');

    const serviceAccount = await this.getCredentials('googleServiceAccount');

    const client = new JWT({
      email: serviceAccount.email,
      key: serviceAccount.privateKey,
      scopes: ['https://www.googleapis.com/auth/drive'],
      subject: user, // Impersonate this user
    });

    const { access_token } = await client.getAccessToken();
    return access_token;
  }

  private async getOAuth2DelegationToken(user: string): Promise<string> {
    // Microsoft Graph delegation example
    const credentials = await this.getCredentials('microsoftOAuth2');

    const tokenEndpoint = 'https://login.microsoftonline.com/common/oauth2/v2.0/token';

    const response = await this.helpers.httpRequest({
      method: 'POST',
      url: tokenEndpoint,
      body: {
        grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
        client_id: credentials.clientId,
        client_secret: credentials.clientSecret,
        assertion: await this.createDelegationAssertion(user),
        requested_token_use: 'on_behalf_of',
        scope: 'https://graph.microsoft.com/.default',
      },
    });

    return response.access_token;
  }
}
```

## Advanced Module Patterns

### Plugin Architecture

```typescript theme={null}
export class PluginNode {
  private plugins = new Map<string, Plugin>();

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const pluginPath = this.getNodeParameter('pluginPath', 0) as string;
    const pluginConfig = this.getNodeParameter('pluginConfig', 0) as any;

    // Load and initialize plugin
    const plugin = await this.loadPlugin(pluginPath, pluginConfig);

    // Execute plugin
    return await plugin.execute(this);
  }

  private async loadPlugin(
    path: string,
    config: any
  ): Promise<Plugin> {
    // Check cache
    if (this.plugins.has(path)) {
      return this.plugins.get(path)!;
    }

    // Validate plugin
    await this.validatePlugin(path);

    // Load plugin module
    const PluginClass = await import(path);

    // Initialize with config
    const plugin = new PluginClass(config);

    // Verify plugin interface
    if (!this.implementsPluginInterface(plugin)) {
      throw new Error('Invalid plugin interface');
    }

    // Cache plugin
    this.plugins.set(path, plugin);

    return plugin;
  }

  private implementsPluginInterface(plugin: any): boolean {
    return (
      typeof plugin.execute === 'function' &&
      typeof plugin.validate === 'function' &&
      typeof plugin.getMetadata === 'function'
    );
  }

  private async validatePlugin(path: string): Promise<void> {
    // Security validation
    const fs = require('fs').promises;
    const crypto = require('crypto');

    // Check plugin signature
    const pluginContent = await fs.readFile(path, 'utf8');
    const signature = await fs.readFile(`${path}.sig`, 'utf8');

    const isValid = this.verifySignature(pluginContent, signature);

    if (!isValid) {
      throw new Error('Plugin signature verification failed');
    }
  }
}

interface Plugin {
  execute(context: IExecuteFunctions): Promise<INodeExecutionData[][]>;
  validate(input: any): boolean;
  getMetadata(): PluginMetadata;
}
```

### Lazy Loading Strategy

```typescript theme={null}
export class LazyLoadingNode {
  private moduleLoaders = new Map<string, () => Promise<any>>();

  constructor() {
    // Register module loaders
    this.registerModuleLoaders();
  }

  private registerModuleLoaders(): void {
    // Define lazy loaders for heavy modules
    this.moduleLoaders.set('tensorflow', async () => {
      const tf = await import('@tensorflow/tfjs-node');
      await tf.ready();
      return tf;
    });

    this.moduleLoaders.set('sharp', async () => {
      return await import('sharp');
    });

    this.moduleLoaders.set('puppeteer', async () => {
      const puppeteer = await import('puppeteer');
      return puppeteer.default;
    });
  }

  async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
    const operation = this.getNodeParameter('operation', 0) as string;

    // Load only required modules
    const requiredModules = this.getRequiredModules(operation);
    const modules = await this.loadModules(requiredModules);

    // Execute with loaded modules
    return await this.executeOperation(operation, modules);
  }

  private getRequiredModules(operation: string): string[] {
    const moduleMap: { [key: string]: string[] } = {
      'imageProcessing': ['sharp'],
      'machineLearning': ['tensorflow'],
      'webScraping': ['puppeteer'],
    };

    return moduleMap[operation] || [];
  }

  private async loadModules(
    moduleNames: string[]
  ): Promise<Map<string, any>> {
    const loaded = new Map<string, any>();

    for (const name of moduleNames) {
      const loader = this.moduleLoaders.get(name);
      if (loader) {
        this.logger.info(`Loading module: ${name}`);
        loaded.set(name, await loader());
      }
    }

    return loaded;
  }
}
```

## Security Best Practices

### Module Security Checklist

<Accordion title="Security Implementation Guide">
  1. **Input Validation**
     ```typescript theme={null}
     // Always validate module names
     const validModulePattern = /^[@a-z0-9][\w.-]*$/i;
     if (!validModulePattern.test(moduleName)) {
       throw new Error('Invalid module name');
     }
     ```

  2. **Use Allowlists**
     ```typescript theme={null}
     const allowedModules = new Set(['lodash', 'moment', 'axios']);
     if (!allowedModules.has(moduleName)) {
       throw new Error('Module not allowed');
     }
     ```

  3. **Sandbox Execution**
     ```typescript theme={null}
     const vm = require('vm');
     const sandbox = { /* limited context */ };
     vm.runInContext(code, sandbox, { timeout: 5000 });
     ```

  4. **Version Pinning**
     ```typescript theme={null}
     // Always use exact versions in production
     const moduleSpec = `${name}@${exactVersion}`;
     ```

  5. **Signature Verification**
     ```typescript theme={null}
     const crypto = require('crypto');
     const verify = crypto.createVerify('RSA-SHA256');
     verify.update(moduleContent);
     const isValid = verify.verify(publicKey, signature);
     ```
</Accordion>

## Performance Considerations

### Module Caching Strategy

```typescript theme={null}
export class CachedModuleLoader {
  private cache = new Map<string, CacheEntry>();
  private maxCacheSize = 100; // MB
  private maxCacheAge = 3600000; // 1 hour

  async loadModule(name: string, version: string): Promise<any> {
    const cacheKey = `${name}@${version}`;
    const cached = this.cache.get(cacheKey);

    // Check cache validity
    if (cached && this.isCacheValid(cached)) {
      cached.lastAccess = Date.now();
      return cached.module;
    }

    // Load module
    const module = await this.loadFromDisk(name, version);

    // Update cache
    this.addToCache(cacheKey, module);

    // Cleanup if needed
    if (this.getCacheSize() > this.maxCacheSize) {
      this.evictLRU();
    }

    return module;
  }

  private isCacheValid(entry: CacheEntry): boolean {
    const age = Date.now() - entry.created;
    return age < this.maxCacheAge;
  }

  private evictLRU(): void {
    let oldest: string | null = null;
    let oldestTime = Date.now();

    for (const [key, entry] of this.cache) {
      if (entry.lastAccess < oldestTime) {
        oldest = key;
        oldestTime = entry.lastAccess;
      }
    }

    if (oldest) {
      this.cache.delete(oldest);
    }
  }
}

interface CacheEntry {
  module: any;
  created: number;
  lastAccess: number;
  size: number;
}
```

## Next Steps

With dynamic credentials and module loading mastered, let's explore error handling and retry strategies:

<Card title="Error Handling & Retry Logic" href="./error-handling" icon="shield-exclamation">
  Build resilient nodes with comprehensive error handling and retry strategies
</Card>
