Back to blog
SecurityJanuary 1, 2024· 12 min read

Cross-Origin Resource Sharing (CORS) Debugging: A Developer's Guide

A practical Fusebox guide to cross-origin resource sharing (cors) debugging.

Cross-Origin Resource Sharing (CORS) Debugging: A Developer's Guide

CORS errors are among the most frustrating issues developers face. That dreaded "Access to fetch at 'X' from origin 'Y' has been blocked by CORS policy" message has caused countless hours of debugging. Yet CORS is essential for web security, preventing malicious scripts from accessing resources they shouldn't. This guide will help you understand, debug, and fix CORS issues efficiently.

Why CORS Matters

In 2018, British Airways suffered a data breach where malicious JavaScript injected into their payment page sent credit card details to a attacker-controlled server. CORS is one of the key defenses against such attacks. However, misconfigured CORS can either break legitimate functionality or open security vulnerabilities.

CORS Debugging Toolkit

Start with this comprehensive CORS analyzer:

// CORS Debugging Toolkit
const CORSDebugger = {
    // Test CORS configuration for a URL
    async testCORS(url, options = {}) {
        const results = {
            url,
            origin: window.location.origin,
            timestamp: new Date().toISOString(),
            preflight: null,
            actual: null,
            errors: [],
            warnings: [],
            recommendations: []
        };
        
        try {
            // Test preflight if needed
            if (options.method && !['GET', 'HEAD', 'POST'].includes(options.method)) {
                results.preflight = await this.testPreflight(url, options);
            }
            
            // Test actual request
            results.actual = await this.testActualRequest(url, options);
            
            // Analyze results
            this.analyzeResults(results);
            
        } catch (error) {
            results.errors.push({
                type: 'NetworkError',
                message: error.message,
                details: 'Request failed completely - might be a network issue or CORS blocking'
            });
        }
        
        return results;
    },
    
    // Test preflight request
    async testPreflight(url, options) {
        const preflightResults = {
            sent: false,
            response: null,
            headers: {}
        };
        
        try {
            // Browsers handle preflight automatically, but we can observe the response
            const response = await fetch(url, {
                method: 'OPTIONS',
                headers: {
                    'Access-Control-Request-Method': options.method || 'GET',
                    'Access-Control-Request-Headers': options.headers ? 
                        Object.keys(options.headers).join(', ') : ''
                }
            });
            
            preflightResults.sent = true;
            preflightResults.status = response.status;
            
            // Capture CORS headers
            const corsHeaders = [
                'access-control-allow-origin',
                'access-control-allow-methods',
                'access-control-allow-headers',
                'access-control-allow-credentials',
                'access-control-max-age',
                'access-control-expose-headers'
            ];
            
            corsHeaders.forEach(header => {
                const value = response.headers.get(header);
                if (value) {
                    preflightResults.headers[header] = value;
                }
            });
            
        } catch (error) {
            preflightResults.error = error.message;
        }
        
        return preflightResults;
    },
    
    // Test actual request
    async testActualRequest(url, options = {}) {
        const requestResults = {
            sent: false,
            response: null,
            headers: {},
            body: null
        };
        
        try {
            const fetchOptions = {
                method: options.method || 'GET',
                headers: options.headers || {},
                mode: options.mode || 'cors',
                credentials: options.credentials || 'same-origin'
            };
            
            if (options.body) {
                fetchOptions.body = options.body;
            }
            
            const response = await fetch(url, fetchOptions);
            
            requestResults.sent = true;
            requestResults.status = response.status;
            requestResults.ok = response.ok;
            
            // Capture response headers
            for (const [key, value] of response.headers.entries()) {
                requestResults.headers[key] = value;
            }
            
            // Try to read body
            try {
                requestResults.body = await response.text();
            } catch (e) {
                requestResults.bodyError = 'Could not read response body';
            }
            
        } catch (error) {
            requestResults.error = error.message;
            requestResults.errorType = error.name;
        }
        
        return requestResults;
    },
    
    // Analyze CORS results
    analyzeResults(results) {
        const { preflight, actual, origin } = results;
        
        // Check if CORS is properly configured
        if (actual.error) {
            if (actual.error.includes('CORS')) {
                results.errors.push({
                    type: 'CORS_BLOCKED',
                    message: 'Request blocked by CORS policy',
                    details: actual.error
                });
                
                results.recommendations.push(
                    'Server needs to include proper Access-Control-Allow-Origin header',
                    'Check if credentials are required and properly configured',
                    'Verify allowed methods and headers match your request'
                );
            }
        }
        
        // Check Access-Control-Allow-Origin
        const allowOrigin = actual.headers?.['access-control-allow-origin'];
        if (allowOrigin) {
            if (allowOrigin === '*') {
                results.warnings.push({
                    type: 'WILDCARD_ORIGIN',
                    message: 'Server allows all origins (*)',
                    details: 'This might be a security risk for authenticated endpoints'
                });
            } else if (allowOrigin !== origin) {
                results.errors.push({
                    type: 'ORIGIN_MISMATCH',
                    message: `Origin ${origin} not allowed`,
                    details: `Server allows: ${allowOrigin}`
                });
            }
        } else if (actual.sent && !actual.error) {
            results.warnings.push({
                type: 'MISSING_CORS_HEADERS',
                message: 'Response missing Access-Control-Allow-Origin header',
                details: 'This might work for simple requests but fail for complex ones'
            });
        }
        
        // Check credentials
        const allowCredentials = actual.headers?.['access-control-allow-credentials'];
        if (allowCredentials === 'true' && allowOrigin === '*') {
            results.errors.push({
                type: 'INVALID_CREDENTIALS_CONFIG',
                message: 'Cannot use credentials with wildcard origin',
                details: 'When credentials are allowed, origin must be explicitly specified'
            });
        }
        
        // Check preflight results
        if (preflight) {
            if (!preflight.headers['access-control-allow-methods']) {
                results.errors.push({
                    type: 'MISSING_ALLOWED_METHODS',
                    message: 'Preflight response missing Access-Control-Allow-Methods',
                    details: 'Server must specify which HTTP methods are allowed'
                });
            }
            
            if (preflight.status !== 200 && preflight.status !== 204) {
                results.errors.push({
                    type: 'PREFLIGHT_FAILED',
                    message: `Preflight request failed with status ${preflight.status}`,
                    details: 'Preflight requests must return 200 or 204 status'
                });
            }
        }
        
        return results;
    }
};

// Example usage
CORSDebugger.testCORS('https://api.example.com/data', {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json',
        'X-Custom-Header': 'value'
    },
    credentials: 'include'
}).then(console.log);

Common CORS Scenarios Debugger

Debug specific CORS scenarios:

// CORS Scenario Tester
const CORSScenarios = {
    // Test simple CORS request
    async testSimpleRequest(url) {
        console.log('Testing Simple CORS Request...');
        
        try {
            const response = await fetch(url);
            const data = await response.text();
            
            return {
                success: true,
                status: response.status,
                corsHeaders: {
                    'access-control-allow-origin': 
                        response.headers.get('access-control-allow-origin')
                },
                dataReceived: data.length > 0
            };
        } catch (error) {
            return {
                success: false,
                error: error.message,
                type: 'Simple request failed'
            };
        }
    },
    
    // Test preflight request
    async testPreflightRequest(url) {
        console.log('Testing Preflight CORS Request...');
        
        try {
            const response = await fetch(url, {
                method: 'PUT',
                headers: {
                    'Content-Type': 'application/json',
                    'X-Custom-Header': 'test'
                },
                body: JSON.stringify({ test: true })
            });
            
            return {
                success: true,
                status: response.status,
                preflightTriggered: true,
                corsHeaders: {
                    'access-control-allow-origin': 
                        response.headers.get('access-control-allow-origin'),
                    'access-control-allow-methods': 
                        response.headers.get('access-control-allow-methods'),
                    'access-control-allow-headers': 
                        response.headers.get('access-control-allow-headers')
                }
            };
        } catch (error) {
            return {
                success: false,
                error: error.message,
                type: 'Preflight request failed',
                hint: 'Server might not handle OPTIONS requests properly'
            };
        }
    },
    
    // Test credentialed request
    async testCredentialedRequest(url) {
        console.log('Testing Credentialed CORS Request...');
        
        try {
            const response = await fetch(url, {
                credentials: 'include'
            });
            
            const allowOrigin = response.headers.get('access-control-allow-origin');
            const allowCredentials = response.headers.get('access-control-allow-credentials');
            
            const issues = [];
            if (allowOrigin === '*') {
                issues.push('Wildcard origin (*) not allowed with credentials');
            }
            if (allowCredentials !== 'true') {
                issues.push('Access-Control-Allow-Credentials must be "true"');
            }
            
            return {
                success: issues.length === 0,
                status: response.status,
                corsHeaders: {
                    'access-control-allow-origin': allowOrigin,
                    'access-control-allow-credentials': allowCredentials
                },
                issues
            };
        } catch (error) {
            return {
                success: false,
                error: error.message,
                type: 'Credentialed request failed'
            };
        }
    },
    
    // Test exposed headers
    async testExposedHeaders(url) {
        console.log('Testing Exposed Headers...');
        
        try {
            const response = await fetch(url);
            const exposeHeaders = response.headers.get('access-control-expose-headers');
            
            const customHeaders = {};
            for (const [key, value] of response.headers.entries()) {
                if (!['content-type', 'content-length', 'cache-control', 
                     'expires', 'last-modified', 'pragma'].includes(key.toLowerCase())) {
                    customHeaders[key] = value;
                }
            }
            
            return {
                success: true,
                exposedHeaders: exposeHeaders ? exposeHeaders.split(',').map(h => h.trim()) : [],
                customHeadersFound: Object.keys(customHeaders),
                accessible: Object.keys(customHeaders).length > 0 || exposeHeaders !== null
            };
        } catch (error) {
            return {
                success: false,
                error: error.message
            };
        }
    },
    
    // Run all tests
    async runAllTests(url) {
        const results = {
            url,
            timestamp: new Date().toISOString(),
            tests: {}
        };
        
        results.tests.simple = await this.testSimpleRequest(url);
        results.tests.preflight = await this.testPreflightRequest(url);
        results.tests.credentialed = await this.testCredentialedRequest(url);
        results.tests.exposedHeaders = await this.testExposedHeaders(url);
        
        // Generate summary
        results.summary = {
            allPassed: Object.values(results.tests).every(t => t.success),
            corsEnabled: results.tests.simple.success || 
                        results.tests.simple.corsHeaders?.['access-control-allow-origin'] !== null,
            supportsCredentials: results.tests.credentialed.success,
            supportsPreflight: results.tests.preflight.success
        };
        
        return results;
    }
};

CORS Configuration Generator

Generate correct CORS configurations:

// CORS Configuration Generator
const CORSConfigGenerator = {
    // Generate server configuration
    generateServerConfig(requirements) {
        const configs = {};
        
        // Express.js / Node.js
        configs.express = `
// Express CORS middleware
app.use((req, res, next) => {
    // Allowed origins
    const allowedOrigins = ${JSON.stringify(requirements.origins || ['*'])};
    const origin = req.headers.origin;
    
    if (allowedOrigins.includes('*') || allowedOrigins.includes(origin)) {
        res.setHeader('Access-Control-Allow-Origin', 
            allowedOrigins.includes('*') ? '*' : origin);
    }
    
    // Credentials
    ${requirements.credentials ? 
        "res.setHeader('Access-Control-Allow-Credentials', 'true');" : ''}
    
    // Allowed methods
    res.setHeader('Access-Control-Allow-Methods', 
        '${requirements.methods?.join(', ') || 'GET, POST, PUT, DELETE, OPTIONS'}');
    
    // Allowed headers
    res.setHeader('Access-Control-Allow-Headers', 
        '${requirements.headers?.join(', ') || 'Content-Type, Authorization'}');
    
    // Exposed headers
    ${requirements.exposeHeaders ? 
        `res.setHeader('Access-Control-Expose-Headers', 
            '${requirements.exposeHeaders.join(', ')}');` : ''}
    
    // Max age
    res.setHeader('Access-Control-Max-Age', '${requirements.maxAge || 86400}');
    
    // Handle preflight
    if (req.method === 'OPTIONS') {
        res.sendStatus(204);
    } else {
        next();
    }
});`;
        
        // Nginx configuration
        configs.nginx = `
# Nginx CORS configuration
location /api {
    ${requirements.origins && requirements.origins.length === 1 && requirements.origins[0] !== '*' ? 
        `if ($http_origin = "${requirements.origins[0]}") {
        add_header 'Access-Control-Allow-Origin' '$http_origin' always;
    }` : 
        "add_header 'Access-Control-Allow-Origin' '*' always;"}
    
    ${requirements.credentials ? 
        "add_header 'Access-Control-Allow-Credentials' 'true' always;" : ''}
    
    add_header 'Access-Control-Allow-Methods' \
        '${requirements.methods?.join(', ') || 'GET, POST, PUT, DELETE, OPTIONS'}' always;
    
    add_header 'Access-Control-Allow-Headers' \
        '${requirements.headers?.join(', ') || 'Content-Type, Authorization'}' always;
    
    ${requirements.exposeHeaders ? 
        `add_header 'Access-Control-Expose-Headers' \
        '${requirements.exposeHeaders.join(', ')}' always;` : ''}
    
    add_header 'Access-Control-Max-Age' ${requirements.maxAge || 86400} always;
    
    if ($request_method = 'OPTIONS') {
        return 204;
    }
}`;
        
        // Apache configuration
        configs.apache = `
# Apache CORS configuration (.htaccess)
<IfModule mod_headers.c>
    ${requirements.origins && requirements.origins[0] !== '*' ? 
        `SetEnvIf Origin "^(${requirements.origins.join('|')})$" ORIGIN_OK=$0
    Header set Access-Control-Allow-Origin "%{ORIGIN_OK}e" env=ORIGIN_OK` :
        'Header set Access-Control-Allow-Origin "*"'}
    
    ${requirements.credentials ? 
        'Header set Access-Control-Allow-Credentials "true"' : ''}
    
    Header set Access-Control-Allow-Methods \
        "${requirements.methods?.join(', ') || 'GET, POST, PUT, DELETE, OPTIONS'}"
    
    Header set Access-Control-Allow-Headers \
        "${requirements.headers?.join(', ') || 'Content-Type, Authorization'}"
    
    ${requirements.exposeHeaders ? 
        `Header set Access-Control-Expose-Headers \
        "${requirements.exposeHeaders.join(', ')}"` : ''}
    
    Header set Access-Control-Max-Age "${requirements.maxAge || 86400}"
</IfModule>

# Handle preflight
RewriteEngine On
RewriteCond %{REQUEST_METHOD} OPTIONS
RewriteRule ^(.*)$ $1 [R=204,L]`;
        
        return configs;
    },
    
    // Generate fetch configuration
    generateFetchConfig(endpoint, options = {}) {
        const config = {
            url: endpoint,
            options: {
                method: options.method || 'GET',
                headers: {},
                mode: 'cors'
            }
        };
        
        // Add headers
        if (options.contentType) {
            config.options.headers['Content-Type'] = options.contentType;
        }
        
        if (options.authorization) {
            config.options.headers['Authorization'] = options.authorization;
        }
        
        if (options.customHeaders) {
            Object.assign(config.options.headers, options.customHeaders);
        }
        
        // Add body
        if (options.body) {
            config.options.body = typeof options.body === 'object' ? 
                JSON.stringify(options.body) : options.body;
        }
        
        // Add credentials
        if (options.includeCredentials) {
            config.options.credentials = 'include';
        }
        
        // Generate code
        const code = `
// CORS-enabled fetch request
fetch('${endpoint}', ${JSON.stringify(config.options, null, 2)})
    .then(response => {
        if (!response.ok) {
            throw new Error(\`HTTP error! status: \${response.status}\`);
        }
        return response.json();
    })
    .then(data => {
        console.log('Success:', data);
    })
    .catch(error => {
        if (error.message.includes('CORS')) {
            console.error('CORS Error:', error);
            console.log('Check the browser console Network tab for details');
        } else {
            console.error('Error:', error);
        }
    });`;
        
        return code;
    }
};

// Example usage
const serverConfig = CORSConfigGenerator.generateServerConfig({
    origins: ['https://example.com', 'https://app.example.com'],
    methods: ['GET', 'POST', 'PUT', 'DELETE'],
    headers: ['Content-Type', 'Authorization', 'X-Request-ID'],
    credentials: true,
    exposeHeaders: ['X-Total-Count', 'X-Page-Number'],
    maxAge: 3600
});

console.log(serverConfig.express);

CORS Proxy Implementation

Create a CORS proxy for development:

// Development CORS Proxy
const CORSProxy = {
    // Create a proxy URL
    createProxyURL(targetURL, proxyServer = 'https://cors-anywhere.herokuapp.com/') {
        return proxyServer + targetURL;
    },
    
    // Local proxy implementation
    createLocalProxy() {
        // This would run in a Node.js environment
        const proxyCode = `
const express = require('express');
const axios = require('axios');
const app = express();

app.use(express.json());

// CORS middleware
app.use((req, res, next) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
    res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Requested-With');
    res.header('Access-Control-Allow-Credentials', 'true');
    
    if (req.method === 'OPTIONS') {
        return res.sendStatus(204);
    }
    next();
});

// Proxy endpoint
app.all('/proxy/*', async (req, res) => {
    const targetURL = req.params[0];
    
    try {
        const response = await axios({
            method: req.method,
            url: targetURL,
            headers: {
                ...req.headers,
                host: new URL(targetURL).host
            },
            data: req.body,
            responseType: 'arraybuffer'
        });
        
        // Forward headers
        Object.entries(response.headers).forEach(([key, value]) => {
            res.set(key, value);
        });
        
        res.status(response.status).send(response.data);
    } catch (error) {
        res.status(error.response?.status || 500).json({
            error: error.message
        });
    }
});

app.listen(3000, () => {
    console.log('CORS proxy running on http://localhost:3000');
    console.log('Usage: http://localhost:3000/proxy/[URL]');
});`;
        
        return proxyCode;
    },
    
    // Browser-based workaround checker
    checkWorkarounds(url) {
        const workarounds = [];
        
        // Check if URL is same-origin
        try {
            const targetURL = new URL(url);
            const currentURL = new URL(window.location.href);
            
            if (targetURL.origin === currentURL.origin) {
                workarounds.push({
                    type: 'same-origin',
                    applicable: true,
                    description: 'URL is same-origin, CORS not needed'
                });
            }
        } catch (e) {
            // Invalid URL
        }
        
        // Check if JSONP is possible
        if (url.includes('callback=') || url.includes('jsonp')) {
            workarounds.push({
                type: 'jsonp',
                applicable: true,
                description: 'API might support JSONP',
                example: `
// JSONP request
function jsonp(url, callback) {
    const script = document.createElement('script');
    const callbackName = 'jsonpCallback_' + Date.now();
    
    window[callbackName] = (data) => {
        callback(data);
        delete window[callbackName];
        document.body.removeChild(script);
    };
    
    script.src = url + (url.includes('?') ? '&' : '?') + 'callback=' + callbackName;
    document.body.appendChild(script);
}`
            });
        }
        
        // Server-side proxy
        workarounds.push({
            type: 'server-proxy',
            applicable: true,
            description: 'Use a server-side proxy',
            example: 'Create an endpoint on your server that fetches the data'
        });
        
        // Browser extension
        workarounds.push({
            type: 'browser-extension',
            applicable: true,
            description: 'Use a CORS-disabling browser extension (development only)',
            warning: 'Never use in production or with sensitive data'
        });
        
        return workarounds;
    }
};

CORS Error Patterns Analyzer

Analyze common CORS error patterns:

// CORS Error Pattern Analyzer
const CORSErrorAnalyzer = {
    patterns: [
        {
            regex: /No 'Access-Control-Allow-Origin' header is present/,
            type: 'MISSING_HEADER',
            description: 'Server is not sending CORS headers',
            solutions: [
                'Add Access-Control-Allow-Origin header on server',
                'Check if server has CORS middleware enabled',
                'Verify the endpoint URL is correct'
            ]
        },
        {
            regex: /CORS policy: The request client is not a secure context/,
            type: 'INSECURE_CONTEXT',
            description: 'Request from non-HTTPS origin to HTTPS endpoint',
            solutions: [
                'Use HTTPS for your development server',
                'Use localhost (which is considered secure)',
                'Configure browser to allow insecure contexts (dev only)'
            ]
        },
        {
            regex: /CORS policy: Cross origin requests are only supported for protocol schemes/,
            type: 'INVALID_PROTOCOL',
            description: 'Invalid protocol in URL',
            solutions: [
                'Ensure URL starts with http:// or https://',
                'Check for typos in the URL',
                'Verify the API endpoint format'
            ]
        },
        {
            regex: /The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard/,
            type: 'WILDCARD_WITH_CREDENTIALS',
            description: 'Cannot use * origin with credentials',
            solutions: [
                'Specify exact origin instead of *',
                'Remove credentials: "include" from request',
                'Configure server to echo the Origin header'
            ]
        },
        {
            regex: /Method .+ is not allowed by Access-Control-Allow-Methods/,
            type: 'METHOD_NOT_ALLOWED',
            description: 'HTTP method not in allowed methods list',
            solutions: [
                'Add method to Access-Control-Allow-Methods',
                'Check if server handles preflight requests',
                'Verify the HTTP method is correct'
            ]
        }
    ],
    
    // Analyze error message
    analyzeError(errorMessage) {
        const analysis = {
            message: errorMessage,
            matches: [],
            recommendations: []
        };
        
        // Check against patterns
        this.patterns.forEach(pattern => {
            if (pattern.regex.test(errorMessage)) {
                analysis.matches.push({
                    type: pattern.type,
                    description: pattern.description,
                    solutions: pattern.solutions
                });
            }
        });
        
        // General recommendations
        if (analysis.matches.length === 0) {
            analysis.recommendations.push(
                'Check browser console for full error details',
                'Verify network connectivity',
                'Check if API endpoint is correct',
                'Test with a simple GET request first'
            );
        }
        
        // Provide debugging steps
        analysis.debuggingSteps = [
            '1. Open Network tab in DevTools',
            '2. Look for OPTIONS request (preflight)',
            '3. Check response headers for CORS headers',
            '4. Verify request headers match allowed headers',
            '5. Check console for detailed error messages'
        ];
        
        return analysis;
    },
    
    // Generate diagnostic report
    generateDiagnosticReport(error, request, response) {
        const report = {
            timestamp: new Date().toISOString(),
            error: this.analyzeError(error),
            request: {
                url: request.url,
                method: request.method,
                headers: request.headers,
                credentials: request.credentials,
                mode: request.mode
            },
            response: response ? {
                status: response.status,
                headers: response.headers,
                corsHeaders: this.extractCORSHeaders(response.headers)
            } : null,
            checklist: this.generateChecklist(request, response)
        };
        
        return report;
    },
    
    // Extract CORS headers
    extractCORSHeaders(headers) {
        const corsHeaders = {};
        const corsKeys = [
            'access-control-allow-origin',
            'access-control-allow-methods',
            'access-control-allow-headers',
            'access-control-allow-credentials',
            'access-control-expose-headers',
            'access-control-max-age'
        ];
        
        corsKeys.forEach(key => {
            if (headers[key]) {
                corsHeaders[key] = headers[key];
            }
        });
        
        return corsHeaders;
    },
    
    // Generate debugging checklist
    generateChecklist(request, response) {
        return {
            client: [
                {
                    item: 'Correct URL protocol (http/https)',
                    checked: request.url.startsWith('http')
                },
                {
                    item: 'Proper request method',
                    checked: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(request.method)
                },
                {
                    item: 'Credentials configuration matches server',
                    checked: request.credentials !== 'include' || response?.headers?.['access-control-allow-credentials'] === 'true'
                }
            ],
            server: [
                {
                    item: 'Access-Control-Allow-Origin header present',
                    checked: !!response?.headers?.['access-control-allow-origin']
                },
                {
                    item: 'Origin matches allowed origins',
                    checked: true // Would need actual check
                },
                {
                    item: 'Preflight requests handled (OPTIONS)',
                    checked: request.method === 'OPTIONS' ? response?.status === 204 : true
                }
            ]
        };
    }
};

// Example usage
const error = "Access to fetch at 'https://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.";
console.log(CORSErrorAnalyzer.analyzeError(error));

Best Practices for CORS

  1. Specify Explicit Origins: Avoid using * for production APIs
  2. Handle Preflight Properly: Ensure OPTIONS requests return quickly
  3. Minimize Preflight Requests: Use simple requests when possible
  4. Cache Preflight Responses: Use Access-Control-Max-Age
  5. Validate Origins Server-Side: Don't trust the Origin header blindly
  6. Use HTTPS: Many CORS features require secure contexts
  7. Keep Headers Minimal: Only expose necessary response headers
  8. Test Thoroughly: Test with different browsers and scenarios
  9. Monitor CORS Errors: Log failed CORS attempts for security
  10. Document CORS Policy: Make your API's CORS policy clear

Common CORS Issues

  • Missing CORS Headers: Server doesn't send required headers
  • Wildcard with Credentials: Using * with credentials enabled
  • Preflight Failures: OPTIONS request not handled correctly
  • Header Restrictions: Custom headers not in allowed list
  • Method Restrictions: HTTP method not allowed
  • Port Differences: Different ports treated as different origins
  • Protocol Mismatch: HTTP to HTTPS requests blocked
  • Localhost Issues: Some browsers treat localhost specially
  • Cache Problems: Browsers caching CORS preflight responses
  • Third-party Cookies: Safari and Firefox restrictions

Remember: CORS is a browser security feature, not a limitation. Understanding how it works helps you build secure APIs while enabling legitimate cross-origin requests. Always implement CORS with security in mind, as overly permissive policies can expose your users to attacks.