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:
go get github.com/gin-contrib/cors
Basic Usage
Here's the simplest way to implement CORS in your Gin application:
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:
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:
config := cors.Config{
AllowOrigins: []string{"https://*.example.com"},
AllowWildcard: true,
// other configurations
}
AllowOriginFunc
For more complex origin validation logic:
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:
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:
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:
// 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:
-
Avoid using
*
in production: Don't use wildcard origins in production environments; specify the exact domains that should have access. -
Limit allowed methods and headers: Only include methods and headers that your API actually needs.
-
Be careful with credentials: If you enable
AllowCredentials
, never use wildcard origins as this creates a security vulnerability. -
Consider using environment variables for CORS configuration in different environments:
// 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
- Official Gin CORS Middleware Repository
- MDN Web Docs: Cross-Origin Resource Sharing
- OWASP CORS Guide
Exercises
-
Create a Gin application with CORS middleware that only allows requests from
http://localhost:3000
andhttps://yourdomain.com
. -
Modify the CORS configuration to allow only
GET
andPOST
methods and only the headersContent-Type
andAuthorization
. -
Create a simple React or Vue frontend on port 3000 that makes requests to your Gin backend and verify that CORS is functioning correctly.
-
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! :)