DEV Community

crow
crow

Posted on

Demystifying CORS: A Practical Guide for Go Developers

A deep dive into what Cross-Origin Resource Sharing (CORS) is, why it exists, and how to correctly configure it in a Go web server using the Gin framework.

If you've ever built a web application with a separate frontend and backend, you've almost certainly run into this dreaded error in your browser's console:

Access to fetch at 'http://localhost:8080/api/v1/join' from origin 'http://localhost:3000' has been blocked by CORS policy...

This error is a rite of passage for web developers. It can be frustrating, and the knee-jerk reaction is often to find a quick fix to "disable" it. But what if I told you that CORS isn't an error to be disabled, but a crucial security feature to be understood and configured correctly?

Let's break down what CORS is, why it exists, and how we've implemented it properly in our Go project using the Gin framework.

The "Why": A Security Rule Called the Same-Origin Policy

To understand CORS, you first need to understand the Same-Origin Policy (SOP). It's a fundamental security rule built into every modern web browser.

Here's a simple analogy: Imagine your backend server is an exclusive, members-only club. The Same-Origin Policy is the club's main rule: "You can only make requests and get data from people already inside this club."

An "origin" is defined by the combination of a protocol (like http), a hostname (like my-game.com), and a port (like :8080).

  • A script on https://my-game.com requesting data from https://my-game.com/api/status is the same origin. The request is allowed.
  • A script on https://my-game.com requesting data from https://api.my-game.com is a different origin (subdomain differs).
  • A script on http://localhost:3000 (your frontend) requesting data from http://localhost:8080 (your backend) is a different origin (port differs).

When your frontend tries to make a request to your backend, the browser sees it's going to a different "club" and, by default, blocks it for security reasons. This prevents a malicious website, say evil.com, from making authenticated requests to your bank's API (your-bank.com/api) using your browser's cookies.

So, how do we allow our legitimate frontend to talk to our backend?

The "How": CORS (Cross-Origin Resource Sharing)

CORS is the mechanism that allows the "club" (your backend server) to tell the browser, "It's okay, I trust that other building (your frontend's origin). You can let their requests through."

It’s not about disabling security; it’s about creating a list of trusted friends.

The "Preflight" Request: A Polite Conversation

For any request that could modify data (like POST, PUT, DELETE, or any request with custom headers like Content-Type: application/json), the browser doesn't send the request immediately. Instead, it sends a "preflight" request.

  1. The Browser Asks: Before sending the real POST request to /api/v1/join, the browser sends a tiny, harmless OPTIONS request to the same URL. It's essentially asking for permission: "Hey server, I'm a script from http://localhost:3000. Are you okay with me sending a POST request with a Content-Type header?"

  2. The Server Responds: Your backend server, if configured for CORS, sees this OPTIONS request and replies with a set of special headers. These headers are the server's answer:

    • Access-Control-Allow-Origin: http://localhost:3000 ("Yes, I allow requests from that specific origin.")
    • Access-Control-Allow-Methods: GET, POST, OPTIONS ("Yes, I allow these HTTP methods.")
    • Access-Control-Allow-Headers: Content-Type, Authorization ("Yes, I allow these headers to be sent.")
  3. The Browser Decides: If the server's response headers grant permission, the browser then sends the actual POST request with the JSON data. If the server doesn't respond with these headers, the browser blocks the request and shows you the infamous CORS error.

CORS in Action: Our Go & Gin Implementation

This whole "conversation" is handled beautifully by middleware. In our project, we use the gin-contrib/cors package. Let's look at the configuration in cmd/server/main.go:

// --- CORS MIDDLEWARE SETUP ---
corsConfig := cors.Config{
    // Here we list our "trusted friends"
    AllowOrigins:     getAllowedOrigins(cfg.Server.AllowedOrigins),

    // The methods we permit
    AllowMethods:     []string{"GET", "POST", "OPTIONS"},

    // The headers our frontend is allowed to send
    AllowHeaders:     []string{"Origin", "Content-Type", "Accept", "Authorization", "Upgrade"},

    ExposeHeaders:    []string{"Content-Length"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}

// Apply this CORS "policy" to all incoming requests
router.Use(cors.New(corsConfig))
// --- END OF CORS SETUP ---
Enter fullscreen mode Exit fullscreen mode

When we call router.Use(cors.New(corsConfig)), we are inserting that "security check" station at the very beginning of our request processing assembly line.

It intercepts every incoming request.
If it's a preflight OPTIONS request, it automatically sends back the correct Access-Control-* headers based on our corsConfig.
If it's a regular GET or POST request, it adds the Access-Control-Allow-Origin header to the response, confirming to the browser that the request was legitimate.
All of this happens before our actual API handlers, like handleRequestJoinNonce, are even touched. This keeps our handler code clean and focused on business logic, while the middleware handles the cross-origin security policy.

Conclusion

CORS is not an error; it's a vital security feature of the web. Understanding the Same-Origin Policy and the preflight request flow turns a frustrating roadblock into a configurable part of your server's security posture. By using a middleware library like gin-contrib/cors, you can implement a robust and secure policy with just a few lines of code, ensuring your frontend and backend can communicate safely and effectively.

Top comments (1)

Collapse
 
stepan_romankov profile image
Stepan Romankov

Bullshit. Instead of avoiding CORS you teach people how to configure it.