Opinionated, caching, retrying fetch client for Node.js with enterprise-grade HTTP features
74
Security features including SSL configuration, certificate validation, and subresource integrity verification for secure HTTP communications.
Configure SSL certificate validation and client certificate authentication:
/**
* SSL/TLS configuration options
*/
interface SSLOptions {
strictSSL?: boolean; // Enable/disable SSL certificate validation (default: true)
ca?: string | Buffer | string[] | Buffer[]; // Certificate authority certificates
cert?: string | Buffer; // Client certificate
key?: string | Buffer; // Client private key
rejectUnauthorized?: boolean; // Alias for strictSSL (internal use)
}Usage Examples:
const fs = require('fs');
const fetch = require('make-fetch-happen');
// Disable SSL verification (not recommended for production)
const response1 = await fetch('https://self-signed.example.com/api', {
strictSSL: false
});
// Custom CA certificate
const response2 = await fetch('https://internal-api.company.com/data', {
ca: fs.readFileSync('./company-ca.pem')
});
// Client certificate authentication
const response3 = await fetch('https://secure-api.example.com/data', {
cert: fs.readFileSync('./client-cert.pem'),
key: fs.readFileSync('./client-key.pem'),
ca: fs.readFileSync('./ca-cert.pem')
});
// Multiple CA certificates
const response4 = await fetch('https://api.example.com/data', {
ca: [
fs.readFileSync('./ca1.pem'),
fs.readFileSync('./ca2.pem'),
fs.readFileSync('./ca3.pem')
]
});SSL behavior respects Node.js environment variables:
/**
* Environment variable support:
* - NODE_TLS_REJECT_UNAUTHORIZED: When set to '0', disables SSL verification
* - This affects the default value of strictSSL option
*
* Priority order:
* 1. Explicit strictSSL option in request
* 2. NODE_TLS_REJECT_UNAUTHORIZED environment variable
* 3. Default (true - SSL verification enabled)
*/Usage Examples:
# Disable SSL verification globally (not recommended for production)
export NODE_TLS_REJECT_UNAUTHORIZED=0
node app.js
# Or per command
NODE_TLS_REJECT_UNAUTHORIZED=0 node app.js// Environment variable affects default behavior
const response = await fetch('https://self-signed.example.com/api');
// If NODE_TLS_REJECT_UNAUTHORIZED=0, SSL verification is disabled
// Explicit override of environment
const response2 = await fetch('https://self-signed.example.com/api', {
strictSSL: true // Forces SSL verification regardless of environment
});Verify response content integrity using cryptographic hashes:
/**
* Subresource Integrity options
*/
interface IntegrityOptions {
integrity?: string; // SRI hash string (sha256-, sha384-, sha512-)
algorithms?: string[]; // Allowed hash algorithms for verification
size?: number; // Expected content size in bytes
}
/**
* Supported SRI hash formats:
* - sha256-<base64-hash>
* - sha384-<base64-hash>
* - sha512-<base64-hash>
* - Multiple hashes separated by spaces
*/Usage Examples:
// Verify file integrity with SHA256
const response1 = await fetch('https://cdn.example.com/library.js', {
integrity: 'sha256-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'
});
// Multiple hash algorithms (any can match)
const response2 = await fetch('https://cdn.example.com/library.js', {
integrity: 'sha256-abc123... sha512-def456...'
});
// With size verification
const response3 = await fetch('https://cdn.example.com/large-file.zip', {
integrity: 'sha256-xyz789...',
size: 1024 * 1024 * 50 // Expect 50MB file
});
// Custom algorithms (limits which hashes are accepted)
const response4 = await fetch('https://cdn.example.com/file.dat', {
integrity: 'sha512-abc123...',
algorithms: ['sha512'] // Only accept SHA512 hashes
});Understanding how integrity verification works:
/**
* Integrity verification process:
* 1. Response body is streamed through integrity verification
* 2. Hash is calculated as data flows through
* 3. Final hash is compared against expected integrity value
* 4. If mismatch, request fails with EINTEGRITY error
* 5. Verification only occurs on 200 responses
*/Usage Examples:
try {
const response = await fetch('https://cdn.example.com/tampered-file.js', {
integrity: 'sha256-expected-hash-that-wont-match'
});
const content = await response.text();
} catch (error) {
if (error.code === 'EINTEGRITY') {
console.log('File integrity verification failed!');
console.log('Expected:', error.expected);
console.log('Actual:', error.actual);
}
}
// Integrity verification with caching
const response = await fetch('https://cdn.example.com/library.js', {
cachePath: './cache',
integrity: 'sha256-abc123...'
});
// Cached responses are also verified against integrity hashCommon patterns for certificate management:
const fs = require('fs');
const path = require('path');
// Certificate loading utility
const loadCertificates = (certDir) => {
return {
ca: fs.readFileSync(path.join(certDir, 'ca.pem')),
cert: fs.readFileSync(path.join(certDir, 'client.pem')),
key: fs.readFileSync(path.join(certDir, 'client-key.pem'))
};
};
// Environment-specific certificate configuration
const createSecureFetch = (env) => {
const config = {};
if (env === 'development') {
// More permissive for development
config.strictSSL = false;
} else if (env === 'staging') {
// Staging certificates
const certs = loadCertificates('./certs/staging');
Object.assign(config, certs);
} else if (env === 'production') {
// Production certificates with strict validation
const certs = loadCertificates('./certs/production');
Object.assign(config, certs, { strictSSL: true });
}
return fetch.defaults(config);
};
// Certificate rotation handling
class CertificateManager {
constructor(certDir) {
this.certDir = certDir;
this.certificates = null;
this.lastLoaded = 0;
this.refreshInterval = 60 * 60 * 1000; // 1 hour
}
loadCertificates() {
const now = Date.now();
if (!this.certificates || (now - this.lastLoaded) > this.refreshInterval) {
this.certificates = {
ca: fs.readFileSync(path.join(this.certDir, 'ca.pem')),
cert: fs.readFileSync(path.join(this.certDir, 'client.pem')),
key: fs.readFileSync(path.join(this.certDir, 'client-key.pem'))
};
this.lastLoaded = now;
}
return this.certificates;
}
createFetch() {
const certs = this.loadCertificates();
return fetch.defaults(certs);
}
}
// Usage
const certManager = new CertificateManager('./certs');
const secureFetch = certManager.createFetch();Handle security-related errors appropriately:
/**
* Security-related error codes:
* - EINTEGRITY: Subresource integrity verification failed
* - DEPTH_ZERO_SELF_SIGNED_CERT: Self-signed certificate
* - UNABLE_TO_VERIFY_LEAF_SIGNATURE: Certificate verification failed
* - CERT_HAS_EXPIRED: Certificate has expired
* - CERT_NOT_YET_VALID: Certificate not yet valid
* - UNABLE_TO_GET_ISSUER_CERT: Cannot get certificate issuer
*/Usage Examples:
try {
const response = await fetch('https://secure-api.example.com/data', {
cert: clientCert,
key: clientKey,
ca: caCert,
integrity: 'sha256-expected-hash'
});
} catch (error) {
switch (error.code) {
case 'EINTEGRITY':
console.log('Response integrity verification failed');
// Log security incident
break;
case 'DEPTH_ZERO_SELF_SIGNED_CERT':
console.log('Server uses self-signed certificate');
// Decide whether to allow or reject
break;
case 'CERT_HAS_EXPIRED':
console.log('Server certificate has expired');
// Alert operations team
break;
case 'UNABLE_TO_VERIFY_LEAF_SIGNATURE':
console.log('Certificate verification failed');
// Security error - do not retry
break;
default:
console.log('Security error:', error.message);
}
}
// Graceful degradation for certificate issues
const fetchWithFallback = async (url, options = {}) => {
try {
return await fetch(url, options);
} catch (error) {
if (error.code === 'DEPTH_ZERO_SELF_SIGNED_CERT' &&
process.env.NODE_ENV === 'development') {
console.warn('Allowing self-signed cert in development');
return await fetch(url, { ...options, strictSSL: false });
}
throw error;
}
};Integrate with content security policies and security headers:
// Generate SRI hashes for CSP
const crypto = require('crypto');
const generateIntegrityHash = (content, algorithm = 'sha256') => {
const hash = crypto.createHash(algorithm);
hash.update(content);
return `${algorithm}-${hash.digest('base64')}`;
};
// Fetch and generate CSP-compatible integrity hash
const response = await fetch('https://cdn.example.com/library.js');
const content = await response.text();
const integrityHash = generateIntegrityHash(content);
console.log(`<script src="https://cdn.example.com/library.js"
integrity="${integrityHash}"
crossorigin="anonymous"></script>`);
// Verify against known good hashes
const knownGoodHashes = {
'library.js': 'sha256-abc123...',
'framework.js': 'sha256-def456...'
};
const secureResponse = await fetch('https://cdn.example.com/library.js', {
integrity: knownGoodHashes['library.js']
});Install with Tessl CLI
npx tessl i tessl/npm-make-fetch-happenevals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9