Skip to main content

Gin CORS Middleware

Introduction

When developing modern web applications, you'll often have a frontend running on one domain (like http://localhost:3000) and an API backend running on another domain (like http://localhost:8080). However, web browsers implement a security feature called the Same-Origin Policy, which prevents JavaScript from making requests to a different domain than the one that served the web page.

Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to specify which origins are permitted to access resources. In the Gin web framework, we can implement CORS using middleware to handle these cross-origin requests properly.

This tutorial will teach you how to implement and configure CORS middleware in your Gin applications.

Understanding CORS

Before diving into the code, let's understand what CORS does:

  • CORS allows servers to specify which origins can access their resources
  • It controls which HTTP methods (GET, POST, etc.) are allowed
  • It manages whether credentials (cookies, HTTP authentication) can be included in requests
  • It determines which headers can be sent or exposed in responses

Without proper CORS configuration, browsers will block cross-origin requests, leading to errors like:

Access to XMLHttpRequest at 'http://api.example.com/users' from origin 'http://example.com' 
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present.

Using Gin's Official CORS Middleware

The Gin framework doesn't have built-in CORS middleware in its core package, but there's an official package called github.com/gin-contrib/cors that handles this functionality.

Installation

First, let's install the Gin CORS middleware package:

bash
go get github.com/gin-contrib/cors

Basic Usage

Here's the simplest way to implement CORS in your Gin application:

go
package main

import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"time"
)

func main() {
router := gin.Default()

// Use default CORS middleware configuration
router.Use(cors.Default())

router.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "This can be accessed from any origin!",
})
})

router.Run(":8080")
}

The cors.Default() configuration:

  • Allows all origins (*)
  • Allows common HTTP methods: GET, POST, PUT, HEAD, DELETE, OPTIONS
  • Allows common headers
  • Does not allow credentials (cookies, etc.)

Custom CORS Configuration

For more control over your CORS policy, you can create a custom configuration:

go
package main

import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"time"
)

func main() {
router := gin.Default()

// Custom CORS configuration
config := cors.Config{
AllowOrigins: []string{"http://localhost:3000", "https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}

router.Use(cors.New(config))

router.GET("/api/data", func(c *gin.Context) {
c.JSON(200, gin.H{
"message": "This endpoint has custom CORS settings!",
})
})

router.Run(":8080")
}

Let's break down this configuration:

  • AllowOrigins: Specifies which origins can access the resources. You can use specific domains or wildcards.
  • AllowMethods: Lists HTTP methods that are allowed when accessing the resource.
  • AllowHeaders: Indicates which headers can be used in the actual request.
  • ExposeHeaders: Headers that can be exposed to the client.
  • AllowCredentials: Whether the request can include user credentials like cookies, HTTP authentication, or client-side SSL certificates.
  • MaxAge: How long the results of a preflight request can be cached.

CORS Configuration Options

The cors package provides several other configuration options:

AllowWildcard

To allow subdomains with a wildcard:

go
config := cors.Config{
AllowOrigins: []string{"https://*.example.com"},
AllowWildcard: true,
// other configurations
}

AllowOriginFunc

For more complex origin validation logic:

go
config := cors.Config{
AllowOriginFunc: func(origin string) bool {
return origin == "https://github.com" || strings.HasPrefix(origin, "https://api.")
},
// other configurations
}

Practical Example: RESTful API with CORS

Here's a more complete example of a RESTful API with CORS middleware:

go
package main

import (
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
"net/http"
"time"
)

type User struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}

var users = []User{
{ID: "1", Name: "John Doe", Email: "[email protected]"},
{ID: "2", Name: "Jane Smith", Email: "[email protected]"},
}

func main() {
r := gin.Default()

// Configure CORS
corsConfig := cors.Config{
AllowOrigins: []string{"http://localhost:3000", "https://mywebsite.com"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"},
AllowHeaders: []string{"Origin", "Authorization", "Content-Type"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}

r.Use(cors.New(corsConfig))

// API routes
r.GET("/api/users", func(c *gin.Context) {
c.JSON(http.StatusOK, users)
})

r.GET("/api/users/:id", func(c *gin.Context) {
id := c.Param("id")

for _, user := range users {
if user.ID == id {
c.JSON(http.StatusOK, user)
return
}
}

c.JSON(http.StatusNotFound, gin.H{"message": "User not found"})
})

r.POST("/api/users", func(c *gin.Context) {
var newUser User

if err := c.ShouldBindJSON(&newUser); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}

// In a real app, you would generate an ID and save to a database
newUser.ID = "3" // Simplified for the example
users = append(users, newUser)

c.JSON(http.StatusCreated, newUser)
})

r.Run(":8080")
}

Testing CORS Configuration

You can test your CORS configuration using various tools:

1. Using curl to test preflight requests:

bash
curl -X OPTIONS -H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type, Authorization" \
-v http://localhost:8080/api/users

2. Using JavaScript in a browser:

javascript
// Run this in a browser console at http://localhost:3000
fetch('http://localhost:8080/api/users', {
method: 'GET',
credentials: 'include',
headers: {
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));

Common CORS Issues and Solutions

1. "No 'Access-Control-Allow-Origin' header is present"

Solution: Make sure your AllowOrigins array includes the origin making the request or use AllowAllOrigins: true.

2. "Method not allowed by CORS"

Solution: Add the required HTTP method to your AllowMethods array.

3. "Request header not allowed by CORS"

Solution: Add the required header to your AllowHeaders array.

4. "Cookies not being sent with request"

Solution: Set AllowCredentials: true and make sure your frontend code includes credentials: 'include' in fetch requests.

Security Considerations

While implementing CORS, keep these security best practices in mind:

  1. Avoid using * in production: Don't use wildcard origins in production environments; specify the exact domains that should have access.

  2. Limit allowed methods and headers: Only include methods and headers that your API actually needs.

  3. Be careful with credentials: If you enable AllowCredentials, never use wildcard origins as this creates a security vulnerability.

  4. Consider using environment variables for CORS configuration in different environments:

go
// Load allowed origins from environment variable
allowedOrigins := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")

config := cors.Config{
AllowOrigins: allowedOrigins,
// other configurations
}

Summary

CORS is an essential security feature for modern web applications that work across different domains. The Gin CORS middleware provides a flexible and powerful way to implement CORS policies in your Go web services:

  • Use cors.Default() for quick development environments
  • Create custom configurations with cors.New(config) for production
  • Configure specific origins, methods, headers, and credential policies
  • Test thoroughly to ensure your API is accessible to legitimate clients while remaining secure

By properly configuring CORS middleware in your Gin applications, you can build secure APIs that communicate seamlessly with frontends hosted on different domains.

Additional Resources

  1. Official Gin CORS Middleware Repository
  2. MDN Web Docs: Cross-Origin Resource Sharing
  3. OWASP CORS Guide

Exercises

  1. Create a Gin application with CORS middleware that only allows requests from http://localhost:3000 and https://yourdomain.com.

  2. Modify the CORS configuration to allow only GET and POST methods and only the headers Content-Type and Authorization.

  3. Create a simple React or Vue frontend on port 3000 that makes requests to your Gin backend and verify that CORS is functioning correctly.

  4. Implement environment-based CORS configuration that uses different settings for development and production environments.



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)