Credentialed CORS Security: How to Configure Cross-Origin APIs Without Exposing User Data https://vulnify.app/blog/credentialed-cors-security-cross-origin-apis Learn how to configure credentialed CORS safely, restrict trusted origins, handle preflight requests, protect cookies, and test cross-origin APIs without exposing user data. Cross-Origin Resource Sharing, usually shortened to CORS, is a browser security mechanism that lets a server opt into selected cross-origin reads. It is essential for modern applications where a frontend, API, identity service, file host, or partner portal lives on a different origin. The danger begins when a configuration created to make development easier becomes broader than the business requirement. A reflected Origin value, an overly permissive allowlist, or credentialed cross-origin access can turn a normal API response into data that an untrusted website is allowed to read. This guide focuses on credentialed CORS, exact origin allowlisting, preflight behavior, common implementation mistakes, and a repeatable way to test the deployed policy. The goal is not to disable cross-origin requests. The goal is to make the trust relationship explicit and narrow enough that the browser is only allowed to expose sensitive responses to frontends the application actually trusts. Start with the same-origin policy Browsers normally restrict script running on one origin from reading responses from another origin. An origin is defined by scheme, host, and port. CORS is the server-controlled mechanism that relaxes that browser restriction for selected requests. The server does this with response headers such as Access-Control-Allow-Origin , and for some requests the browser first sends an OPTIONS preflight. CORS is not a firewall. It does not stop a server from receiving a request, and it does not replace authentication or authorization. A backend must still decide whether the caller is allowed to perform an action. CORS decides whether browser JavaScript from another origin is permitted to read the response. That distinction matters because a route can be protected correctly by authentication but still expose authenticated data to the wrong browser origin if its CORS policy is too broad. Why credentialed CORS needs special care A credentialed browser request can include cookies, HTTP authentication, or other browser-managed credentials. When a server permits a foreign origin to read a credentialed response, the trust decision becomes much more important because the response may contain account-specific data. Access-Control-Allow-Origin: https://app.example.com Access-Control-Allow-Credentials: true This can be appropriate when https://app.example.com is the intended frontend for the API. The risky pattern is dynamically copying an arbitrary Origin header into Access-Control-Allow-Origin without verifying that the origin is trusted. In that design, any attacker-controlled website can potentially nominate itself as trusted. Use an exact origin allowlist A strong CORS design starts with the smallest set of origins that genuinely require browser access. Compare complete origins rather than using substring checks. The string trusted.example.com.attacker.test contains a trusted-looking name but is not the trusted origin. const allowedOrigins = new Set([ 'https://app.example.com', 'https://admin.example.com' ]); function approvedOrigin(origin) { return allowedOrigins.has(origin) ? origin : null; } In a real framework, use its supported CORS middleware rather than copying this pseudocode directly. The important rule is that approval comes from server-controlled configuration, not from attacker-controlled request data. If development environments need additional origins, configure them separately instead of adding a wildcard or broad suffix rule to production. Understand wildcard behavior The wildcard value * can be valid for genuinely public resources that do not need credentials. It is not a shortcut for a private authenticated API. Browsers do not allow the wildcard origin to be combined with credentialed access in the way developers sometimes expect, and attempts to work around that limitation by reflecting origins can create a real exposure. Review each route separately. A public image metadata API may safely allow broad reads while an account endpoint on the same service should have a narrow policy. Applying one global CORS rule to an entire application is convenient but often too coarse. What a preflight actually tells you For requests that are not within the browser's simple-request rules, the browser sends an OPTIONS request before the actual request. The preflight can include Access-Control-Request-Method and Access-Control-Request-Headers . The server responds with the methods, headers, and origin it is willing to allow. OPTIONS /api/profile HTTP/1.1 Origin: https://app.example.com Access-Control-Request-Method: PUT Access-Control-Request-Headers: Content-Type, X-CSRF-Token A successful preflight does not authorize the future PUT . The application must still authenticate the request and verify the user's permission to change the profile. This is one reason CORS should not be used as a substitute for endpoint authorization. CORS is not CSRF protection CORS and cross-site request forgery solve different problems. A browser may be able to send certain cross-site requests even when the response cannot be read by the attacking page. This is why state-changing routes need proper CSRF protections when they rely on ambient browser credentials. SameSite cookies, anti-CSRF tokens, and Origin or Referer validation can all contribute depending on the application design. Do not weaken CORS simply to make a CSRF problem disappear, and do not assume a strict CORS allowlist means state-changing requests are automatically safe. Authorization, CSRF defenses, and CORS should each be designed for their own purpose. Treat the null origin deliberately Some browser contexts can send an Origin value of null , including sandboxed documents and certain local or opaque contexts. Allowing null because it looks non-hostile can create unexpected trust. Only allow it when the application has a documented requirement and the consequences are understood. Hypothetical scenario: SaaS frontend and API A SaaS company serves its customer portal from https://app.example.com and its API from https://api.example.com . During development, the team writes middleware that echoes any incoming Origin so local frontends work without configuration. They also enable credentials because the application uses a session cookie. In production, an attacker hosts JavaScript on another website and causes a logged-in customer to visit it. The attacker's page sends a credentialed fetch request to the API. Because the API reflects the attacker's Origin and permits credentials, the browser allows the attacker's JavaScript to read the account response. The correct fix is not to block one attacker domain. The team replaces origin reflection with an exact allowlist for the production and approved development origins, separates public endpoints from authenticated endpoints, and tests the resulting policy from the public edge. It also reviews state-changing routes to make sure they have independent CSRF and authorization controls. How to test CORS safely 1. Map routes that need cross-origin access List frontend origins, API origins, authentication flows, uploads, WebSocket endpoints, and partner integrations. Security review is easier when you know which cross-origin relationships are intentional. Record development, staging, and production separately because a permissive development policy should not become a production default. 2. Check the public response Use the Vulnify CORS Checker against representative routes. Review Access-Control-Allow-Origin , credential behavior, reflected origins, and preflight handling. Test more than the homepage because CORS is usually relevant to API routes and authenticated flows. 3. Test an untrusted origin In an authorized environment, send requests with an Origin value that is not on the allowlist. The response should not grant that origin access. Use a harmless test origin you control and avoid interacting with real user data. The objective is to validate policy, not to collect sensitive information. 4. Test credential behavior Confirm that routes intended to be public do not accidentally require or expose credentials, and that private routes permit only approved origins. Review cookie scope with the Cookie Security Checker when sessions are involved. 5. Retest after proxy or CDN changes CORS headers may be added by application middleware, an API gateway, a reverse proxy, or an edge platform. A correct configuration in source code does not prove the final public response is correct. Test the hostname and path users actually access. Common CORS mistakes Reflecting every Origin value instead of validating an exact allowlist. Using loose suffix or substring matching for trusted domains. Applying one permissive policy to both public and authenticated endpoints. Allowing more methods or request headers than the application needs. Treating CORS as authentication or authorization. Assuming a preflight response protects a state-changing action. Adding null to the allowlist without understanding why it appears. Fixing browser errors by broadening policy instead of correcting the intended trust relationship. Testing only application code and not the response delivered through the CDN or proxy. How Vulnify fits into a CORS review The CORS Checker is the focused starting point for origin and credential behavior. The broader Website Security Scanner can review CORS alongside other supported public-surface issues such as headers, cookies, redirects, TLS, exposed paths, technology disclosure, and injection indicators. CORS testing still requires application context. Vulnify can show what the public server returns, but your team must decide which origins should be trusted and which user data or actions each route is allowed to expose. Authenticated business logic and role authorization still need application-aware testing. Credentialed CORS checklist Document every trusted browser origin. Use exact origin matching. Separate public and authenticated route policies. Enable credentials only where required. Keep allowed methods and headers narrow. Protect state-changing requests independently from CORS. Review null origin handling. Test API routes from trusted and untrusted origins. Verify the final public response after CDN and proxy processing. Retest when hostnames, authentication, or frontend architecture changes. Conclusion Secure CORS is a trust-boundary exercise, not a header-copying exercise. Decide which browser origins need access, keep that list narrow, separate public and authenticated resources, and remember that CORS does not replace server-side authorization or CSRF defenses. Test the behavior that users actually receive at the public edge and retest whenever the application, proxy, authentication model, or frontend topology changes.