CORS, Security & Rate Limiting
Production-grade HTTP security middleware โ CORS, CSRF protection, secure headers, request body limits, gzip compression, and in-memory or Redis-backed rate limiting.
CORS Middleware
Controls which origins can make cross-domain requests to your server:
import "github.com/CodeSyncr/nimbus/middleware"
// Allow a single origin
app.Router.Use(middleware.CORS("https://app.example.com"))
// Allow all origins (development only)
app.Router.Use(middleware.CORS("*"))
Headers Set
| Header | Value |
|---|---|
Access-Control-Allow-Origin | The configured origin |
Access-Control-Allow-Methods | GET, POST, PUT, PATCH, DELETE, OPTIONS |
Access-Control-Allow-Headers | Content-Type, Authorization |
Preflight OPTIONS requests are automatically handled with 204 No Content.
Per-Group CORS
// API allows different origin than the main app
api := app.Router.Group("/api")
api.Use(middleware.CORS("https://mobile.myapp.com"))
CORS with Credentials
If your frontend sends cookies cross-origin, you need Access-Control-Allow-Credentials. Write a custom middleware:
func CORSWithCredentials(origin string) router.Middleware {
return func(next router.HandlerFunc) router.HandlerFunc {
return func(c *http.Context) error {
c.Response.Header().Set("Access-Control-Allow-Origin", origin)
c.Response.Header().Set("Access-Control-Allow-Credentials", "true")
c.Response.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
c.Response.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
if c.Request.Method == "OPTIONS" {
c.Status(204)
return nil
}
return next(c)
}
}
}
Security Headers
The SecureHeaders middleware sets OWASP-recommended HTTP security headers:
// Use production defaults
app.Router.Use(middleware.SecureHeaders(middleware.DefaultSecureHeadersConfig()))
// Or customize
app.Router.Use(middleware.SecureHeaders(middleware.SecureHeadersConfig{
HSTS: "max-age=63072000; includeSubDomains",
ContentTypeNoSniff: true,
FrameOptions: "DENY",
XSSProtection: "1; mode=block",
ReferrerPolicy: "strict-origin-when-cross-origin",
ContentSecurityPolicy: "default-src 'self'; script-src 'self' 'unsafe-inline'",
PermissionsPolicy: "camera=(), microphone=(), geolocation=()",
}))
| Header | Default | Purpose |
|---|---|---|
Strict-Transport-Security | max-age=63072000; includeSubDomains | Force HTTPS |
X-Content-Type-Options | nosniff | Prevent MIME sniffing |
X-Frame-Options | DENY | Prevent clickjacking |
X-XSS-Protection | 1; mode=block | XSS filter (legacy) |
Referrer-Policy | strict-origin-when-cross-origin | Control referrer leaking |
Content-Security-Policy | (configurable) | Restrict resource loading |
Permissions-Policy | (configurable) | Restrict browser APIs |
CSRF Protection
Protects against Cross-Site Request Forgery by requiring a valid token on POST/PUT/PATCH/DELETE:
// Create a token store
csrfStore := middleware.NewMemoryCSRFStore()
// Apply middleware
app.Router.Use(middleware.CSRF(csrfStore))
Including the Token
In .nimbus templates with the Shield plugin, use {{ .csrfField }} which renders a hidden input automatically:
<form method="POST" action="/posts">
{{ .csrfField }}
<input type="text" name="title">
<button type="submit">Create</button>
</form>
In JavaScript SPAs, send the token via header:
fetch('/api/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': token,
},
body: JSON.stringify({ title: 'Hello' }),
});
The middleware checks for the token in the _csrf form field or the X-CSRF-Token header.
For multi-instance deployments, implement the CSRFStore interface backed by Redis or your database instead of the in-memory store.
Rate Limiting
In-Memory
// 100 requests per minute, keyed by client IP
app.Router.Use(middleware.RateLimit(100, time.Minute, middleware.DefaultKeyFn))
Redis-Backed (multi-instance)
import "github.com/redis/go-redis/v9"
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
app.Router.Use(middleware.RateLimitRedis(rdb, 1000, time.Hour, middleware.DefaultKeyFn))
Custom Key Function
// Rate limit by API key
app.Router.Use(middleware.RateLimit(1000, time.Hour, func(r *http.Request) string {
return r.Header.Get("X-API-Key")
}))
// Strict limit on login
loginLimiter := middleware.RateLimit(5, time.Minute, middleware.DefaultKeyFn)
auth := app.Router.Group("/auth")
auth.Use(loginLimiter)
auth.Post("/login", controllers.Login)
DefaultKeyFn extracts the client IP, handling X-Forwarded-For and X-Real-IP headers for reverse proxy setups.
Body Limit
Prevents denial-of-service by capping request body size:
app.Router.Use(middleware.BodyLimit(10 << 20)) // 10 MB global
// Larger limit for file upload routes
upload := app.Router.Group("/upload")
upload.Use(middleware.BodyLimit(50 << 20)) // 50 MB
Gzip Compression
Transparent gzip compression when the client sends Accept-Encoding: gzip:
app.Router.Use(middleware.Gzip())
Recommended Middleware Stack
A production-ready order (middleware executes top-to-bottom on requests, bottom-to-top on responses):
app := nimbus.New()
// 1. Recovery โ catch panics first
app.Router.Use(middleware.Recover())
// 2. Logging โ log all requests
app.Router.Use(middleware.Logger())
// 3. Security headers โ set on every response
app.Router.Use(middleware.SecureHeaders(middleware.DefaultSecureHeadersConfig()))
// 4. CORS โ handle preflight before other middleware
app.Router.Use(middleware.CORS(os.Getenv("CORS_ORIGIN")))
// 5. Body limit โ reject oversized requests early
app.Router.Use(middleware.BodyLimit(10 << 20))
// 6. Gzip โ compress responses
app.Router.Use(middleware.Gzip())
// 7. Rate limiting โ throttle abuse
app.Router.Use(middleware.RateLimit(100, time.Minute, middleware.DefaultKeyFn))
// 8. CSRF โ protect forms
app.Router.Use(middleware.CSRF(middleware.NewMemoryCSRFStore()))
Place Recover first so it catches panics from all downstream middleware.