go
69 lines · 9 steps
Building a CORS middleware in Gin
A configurable Gin middleware that validates the request Origin and emits the right CORS headers, short-circuiting preflight requests.
Explained by
highlit
1package middleware
2
3import (
4 "net/http"
5 "strconv"
6 "strings"
7 "time"
8
9 "github.com/gin-gonic/gin"
10)
11
12type CORSConfig struct {
13 AllowOrigins []string
14 AllowMethods []string
15 AllowHeaders []string
16 AllowCredentials bool
17 MaxAge time.Duration
18}
19
20func CORS(cfg CORSConfig) gin.HandlerFunc {
21 allowed := make(map[string]struct{}, len(cfg.AllowOrigins))
22 wildcard := false
23 for _, o := range cfg.AllowOrigins {
24 if o == "*" {
25 wildcard = true
26 }
27 allowed[o] = struct{}{}
28 }
29
30 methods := strings.Join(cfg.AllowMethods, ", ")
31 headers := strings.Join(cfg.AllowHeaders, ", ")
32 maxAge := strconv.Itoa(int(cfg.MaxAge.Seconds()))
33
34 return func(c *gin.Context) {
35 origin := c.GetHeader("Origin")
36 if origin == "" {
37 c.Next()
38 return
39 }
40
41 _, ok := allowed[origin]
42 if !ok && !wildcard {
43 c.Next()
44 return
45 }
46
47 h := c.Writer.Header()
48 if wildcard && !cfg.AllowCredentials {
49 h.Set("Access-Control-Allow-Origin", "*")
50 } else {
51 h.Set("Access-Control-Allow-Origin", origin)
52 h.Add("Vary", "Origin")
53 }
54
55 if cfg.AllowCredentials {
56 h.Set("Access-Control-Allow-Credentials", "true")
57 }
58
59 if c.Request.Method == http.MethodOptions {
60 h.Set("Access-Control-Allow-Methods", methods)
61 h.Set("Access-Control-Allow-Headers", headers)
62 h.Set("Access-Control-Max-Age", maxAge)
63 c.AbortWithStatus(http.StatusNoContent)
64 return
65 }
66
67 c.Next()
68 }
69}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Precomputing lookup structures and joined strings once at setup keeps the per-request handler cheap.
- 2CORS requires echoing a specific Origin (plus a Vary header) rather than a wildcard whenever credentials are allowed.
- 3Preflight OPTIONS requests should be answered and aborted early with 204 instead of reaching route handlers.
Related explainers
go
package theme import ( "fmt"
Per-tenant HTML templates with Gin's renderer
multi-tenancy
concurrency
html-templates
Advanced
8 steps
go
package auth import ( "net/http"
Setting and reading secure session cookies in Go
cookies
session-management
security
Intermediate
6 steps
php
<?php namespace App\Http\Middleware;
Idempotent requests with Laravel middleware
idempotency
middleware
caching
Advanced
8 steps
typescript
type ResizeCallback = (size: { width: number; height: number }) => void; export function observeResize(callback: ResizeCallback, delay = 150) { let timeoutId: ReturnType<typeof setTimeout> | undefined;
Debouncing window resize in TypeScript
debounce
closures
event-listeners
Intermediate
6 steps
go
package middleware import ( "net/http"
A maintenance-mode gate in Gin
middleware
atomic-flag
concurrency
Intermediate
8 steps
rust
use axum::{ extract::State, routing::{get, post, MethodRouter}, Json, Router,
Self-documenting routes in Axum
builder-pattern
closures
shared-state
Intermediate
8 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-cors-middleware-in-gin-explained-go-1ab2/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.