How to Handle CORS in Gin with Custom Configuration: Complete Middleware Guide
Use the gin-contrib/cors package to generate a configurable gin.HandlerFunc via cors.New(config), then register it globally or per-group using router.Use() to intercept requests, validate origins against your whitelist, and automatically handle OPTIONS preflight responses.
Handling Cross-Origin Resource Sharing (CORS) properly is essential for any Go web service that accepts browser-based requests. While the gin-gonic/gin framework does not ship with built-in CORS functionality, it provides a flexible middleware architecture that makes it straightforward to handle CORS in Gin with custom configuration using the official community package.
Understanding Gin's Middleware Architecture
Gin processes requests through a HandlersChain assembled for every matched route. According to the source code in routergroup.go, this chain consists of three tiers:
- Global middleware registered with
router.Use(...)– executes for every incoming request. - Group-level middleware attached to a
RouterGroup– executes only for routes sharing that prefix. - Route-specific middleware supplied during route registration.
CORS is implemented as middleware that intercepts the request before your route handler executes. It inspects the Origin header, applies your security policy, sets the necessary response headers (Access-Control-Allow-Origin, Access-Control-Allow-Methods, etc.), and may short-circuit the chain for preflight OPTIONS requests.
Installing the Official CORS Package
Gin relies on the gin-contrib/cors repository for CORS functionality. Install it alongside Gin:
go get github.com/gin-gonic/gin
go get github.com/gin-contrib/cors
Implementing Custom CORS Configuration
The cors.Config struct in cors.go exposes granular controls for origins, methods, headers, credentials, and preflight caching. You convert this configuration into middleware using cors.New(config), which returns a standard gin.HandlerFunc.
Global CORS Setup
Register CORS middleware at the router level to apply a uniform policy across all endpoints:
package main
import (
"time"
"github.com/gin-contrib/cors"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
corsConfig := cors.Config{
AllowOrigins: []string{"https://example.com", "https://api.example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE"},
AllowHeaders: []string{"Origin", "Content-Type", "Authorization"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
r.Use(cors.New(corsConfig))
r.GET("/data", func(c *gin.Context) {
c.JSON(200, gin.H{"data": "response"})
})
r.Run(":8080")
}
Key implementation details from cors.go:
AllowOriginssupports exact strings, wildcards (*), or sub-domain patterns.MaxAgedefines how long browsers may cache preflight results.AllowCredentials: truepermits cookies and authorization headers in cross-origin requests.
Route Group Specific Policies
Because RouterGroup.Use() in routergroup.go accepts the same HandlerFunc interface, you can attach different CORS configurations to distinct API segments:
api := r.Group("/api")
api.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://client-app.com"},
AllowMethods: []string{"GET", "POST"},
}))
admin := r.Group("/admin")
admin.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://admin.example.com"},
AllowMethods: []string{"GET", "POST", "DELETE"},
AllowCredentials: true,
}))
This approach isolates public API CORS rules from administrative endpoints without code duplication.
Manual CORS Implementation
For scenarios requiring dynamic origin validation logic that exceeds the Config struct capabilities, implement a custom gin.HandlerFunc:
import (
"net/http"
"strings"
)
func CustomCORSMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
origin := c.Request.Header.Get("Origin")
// Allow any sub-domain of example.com
if strings.HasSuffix(origin, ".example.com") {
c.Header("Access-Control-Allow-Origin", origin)
c.Header("Access-Control-Allow-Methods", "GET,POST,OPTIONS")
c.Header("Access-Control-Allow-Headers", "Content-Type,Authorization")
if c.Request.Method == http.MethodOptions {
c.AbortWithStatus(http.StatusNoContent)
return
}
}
c.Next()
}
}
Register this with r.Use(CustomCORSMiddleware()) exactly as you would the official package.
Internal Mechanics of CORS Handling
The gin-contrib/cors middleware operates as follows, based on the implementation in cors.go:
-
Configuration Parsing: The
New(config)function validates yourConfigstruct and returns a closure matching thefunc(*gin.Context)signature required by Gin's middleware chain. -
Origin Validation: For each request, the handler extracts the
Originheader and matches it againstAllowedOrigins. It supports exact matches, the wildcard*, and pattern matching for sub-domains. -
Header Injection: Upon a successful match, it writes the configured CORS headers to the response writer before calling
c.Next()to proceed to subsequent handlers. -
Preflight Termination: When the request method is
OPTIONSand theAccess-Control-Request-Methodheader is present, the middleware validates the preflight request. If valid, it callsc.AbortWithStatus(http.StatusNoContent)(HTTP 204), preventing any downstream route handlers from executing and avoiding unnecessary processing.
Summary
- Gin's middleware architecture in
routergroup.gosupports CORS at global, group, or route levels via theUse()method. - The official
gin-contrib/corspackage provides a production-readyConfigstruct andNew()constructor to generategin.HandlerFuncmiddleware. - Custom configurations control allowed origins, methods, headers, credentials, and preflight caching duration.
- The middleware automatically handles OPTIONS preflight requests by aborting the chain with status 204, ensuring compliant browser behavior.
- For complex logic, implement manual middleware using
c.Header()andc.AbortWithStatus().
Frequently Asked Questions
Does Gin include built-in CORS middleware?
No. The gin-gonic/gin repository intentionally keeps the core framework minimal. CORS functionality resides in the community-maintained gin-contrib/cors package, which follows Gin's middleware conventions and integrates seamlessly with the HandlersChain system defined in routergroup.go.
How do I configure CORS to allow credentials?
Set AllowCredentials: true in your cors.Config struct. When enabled, the middleware sets the Access-Control-Allow-Credentials: true header. You must also specify explicit origins in AllowOrigins (wildcard * is incompatible with credentials per the CORS specification). Example:
config := cors.Config{
AllowOrigins: []string{"https://trusted-site.com"},
AllowCredentials: true,
}
Can I apply different CORS rules to different API endpoints?
Yes. Use RouterGroup instances to scope middleware. As shown in routergroup.go, each group maintains its own middleware chain. Attach distinct cors.New(config) calls to different groups (e.g., /api vs /admin), or apply middleware directly to individual route definitions for endpoint-specific policies.
Why do OPTIONS preflight requests return 404 errors?
This occurs when no route handler matches the OPTIONS method, or when CORS middleware is not registered early enough in the chain. Ensure you register cors.New(config) globally or within the specific group before defining routes. The middleware intercepts OPTIONS requests and aborts with status 204 before the router attempts to match a non-existent OPTIONS handler.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →