DEV Community

Jean Klebert de A Modesto
Jean Klebert de A Modesto

Posted on

CORS: The Traffic Guard You Actually Need (And How It Protects User Sessions)

If you’ve ever built a modern web API, you’ve likely been haunted by the infamous error: "No 'Access-Control-Allow-Origin' header is present on the requested resource." While it feels like a hurdle during development, that error is actually the browser acting as a high-tech bodyguard for your users' data.

The Attack Vector: Cross-Site Data Theft

Imagine you are logged into your online bank at bank.com. Your browser stores a session cookie to keep you logged in. Now, suppose you accidentally visit a malicious site, evil-hacker.net, in another tab.

Without CORS and the Same-Origin Policy (SOP), a script on the hacker's site could trigger a fetch request to bank.com/api/account-details. Since browsers automatically attach cookies to requests for the destination domain, the hacker could theoretically read your balance or execute transactions using your active session.

How CORS Stops the Bleeding

CORS (Cross-Origin Resource Sharing) doesn't necessarily stop a request from leaving the browser, but it strictly dictates who is allowed to read the response. It’s a handshake between the browser and the server.

  1. Simple Requests: The browser sends the request and checks the Access-Control-Allow-Origin header in the response. If the domain doesn't match, the browser drops the data before the script can touch it.

  2. Preflight (OPTIONS): For "risky" actions (like POST with JSON or DELETE), the browser sends a "pre-check" request: "Hey server, is this random site allowed to send you this command?" If the server says no, the actual request is never even sent.


Implementing Modern Protection in Symfony

In the Symfony ecosystem, we don't manually manage these headers in every controller. We use the NelmioCorsBundle. Here is how to configure your application to prevent session hijacking.

1. Installation

Bash

composer require nelmio/cors-bundle

Enter fullscreen mode Exit fullscreen mode

2. Security Configuration (config/packages/nelmio_cors.yaml)

Suppose your frontend lives at https://my-app.com. You want to ensure no other origin can make authenticated calls to your API.

YAML

nelmio_cors:
    defaults:
        origin_regex: true
        allow_origin: ['^https://my-app\.com$'] # Only allow your official frontend
        allow_methods: ['GET', 'OPTIONS', 'POST', 'PUT', 'PATCH', 'DELETE']
        allow_headers: ['Content-Type', 'Authorization']
        expose_headers: ['X-Custom-Auth-Header']
        max_age: 3600
    paths:
        '^/api/':
            allow_origin: ['https://my-app.com']
            allow_methods: ['POST', 'PUT', 'GET', 'DELETE']
            allow_credentials: true # Crucial for session protection!

Enter fullscreen mode Exit fullscreen mode

Why allow_credentials: true is the Key

When you set allow_credentials: true, you tell the browser it’s okay to share cookies and authorization headers. However, for security reasons, when this is active, allow_origin cannot be a wildcard (*).

You must specify the exact domain. This prevents any random site from making "credentialed" calls to your backend using the user's existing session.

A Protected Controller Example

With the bundle configured, your Symfony controllers stay clean. The security logic is handled at the architectural level.

PHP

// src/Controller/AccountController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\HttpFoundation\JsonResponse;

class AccountController extends AbstractController
{
    #[Route('/api/balance', name: 'api_balance', methods: ['GET'])]
    public function getBalance(): JsonResponse
    {
        // If CORS is misconfigured, a malicious site could 
        // trigger this, but the browser would prevent the 
        // malicious script from ever reading this JSON response.
        return new JsonResponse([
            'balance' => 1250.75,
            'currency' => 'USD'
        ]);
    }
}

Enter fullscreen mode Exit fullscreen mode

Final Thoughts

CORS isn't a network firewall; it’s a browser-side security policy. By properly configuring nelmio-cors-bundle in your Symfony project, you aren't just fixing a "bug"—you are ensuring that your users' sessions and sensitive data remain locked within your trusted environment.

Top comments (1)

Collapse
 
jeandevbr profile image
Jean Klebert de A Modesto