This episode strengthens the API's defenses: strict CORS, security headers like CSP and HSTS, CSRF, BodyLimit, rate limiting, strict input validation, sanitization, preventing injection and SSRF, and protecting sensitive headers from information leaks.

Security isn't a feature added at the end — it's a design decision. This episode lays out layers of defense: Echo's built-in security middleware to shrink the attack surface, and input hardening to reject malicious data before it reaches business logic.
Episode 14 covers strict CORS, security headers, CSRF, BodyLimit, rate limiting, input validation, sanitization, preventing injection and SSRF, and protecting sensitive headers.
CORS was started in episode 6; now here's the strict version. Never use AllowOrigins: ["*"] when the API carries credentials:
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://app.example.com"},
AllowCredentials: true,
MaxAge: 3600,
ExposeHeaders: []string{echo.HeaderXRequestID},
}))AllowCredentials together with an explicit origin list ensures only known domains can send credentials.
Security headers enforce rules on the browser side. Install them manually in your own middleware:
func securityHeaders() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
h := c.Response().Header()
h.Set(echo.HeaderContentSecurityPolicy,
"default-src 'self'; frame-ancestors 'none'")
h.Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
h.Set("X-Content-Type-Options", "nosniff")
h.Set("X-Frame-Options", "DENY")
return next(c)
}
}
}CSP restricts which content sources the browser can load, HSTS forces HTTPS, nosniff prevents file-type misinterpretation, and X-Frame-Options: DENY blocks clickjacking.
CSRF attacks force the victim's browser to send malicious requests. Echo's CSRF middleware requires a special token for mutation requests:
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:csrf",
CookieName: "_csrf",
CookieHTTPOnly: true,
}))Clients must send the CSRF token (from the cookie) in the body or header of every POST, PUT, and DELETE request.
These two middleware were already discussed — now understand why they must work side by side. BodyLimit bounds the payload size that can be attacked; the rate limiter stops brute-force attempts before they exhaust resources:
e.Use(middleware.BodyLimit("512K"))
e.Use(middleware.RateLimiterWithConfig(middleware.RateLimiterConfig{
Store: middleware.NewRateLimiterMemoryStore(10),
}))For login and password-reset endpoints, consider stricter rate limits or per-account lockout.
Input is untrusted until proven otherwise. Combine the validator (episode 5) with value restrictions:
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email,max=254"`
Age int `json:"age" validate:"required,gte=17,lte=100"`
Role string `json:"role" validate:"oneof=admin member"`
}The oneof rule restricts enum values, and max and lte bound length and range. Server-side validation is always mandatory even if the frontend already validates.
Two attack classes that often break through APIs:
func validateTargetURL(raw string) (string, error) {
u, err := url.Parse(raw)
if err != nil {
return "", err
}
if u.Scheme != "https" {
return "", echo.NewHTTPError(http.StatusBadRequest, "hanya https diizinkan")
}
ip := net.ParseIP(u.Hostname())
if ip != nil && ip.IsPrivate() {
return "", echo.NewHTTPError(http.StatusBadRequest, "target internal ditolak")
}
return u.String(), nil
}Don't leak implementation details. Echo shows a version banner by default — turn it off, and make sure the server header doesn't reveal the version:
e.HideBanner = true
e.HidePort = trueOther protections: never return a stack trace to the client (episode 11), and make sure internal errors don't contain credentials or database connections. Use a request_id to link client errors to internal logs.
curl -I http://localhost:8080/Episode 14 lays out layered defenses: a strict CORS whitelist, CSP and HSTS security headers, CSRF for state mutations, BodyLimit and rate limiting, strict validation with oneof and range rules, preventing injection and SSRF, and protecting sensitive headers from information leaks.
Key takeaways:
BodyLimit and the rate limiter protect against abuse.In episode 15 next, we'll discuss HTTPS, proxy & advanced networking — manual TLS with e.StartTLS, Auto TLS via Let's Encrypt with middleware.AutoTLS, HTTP/2, nginx and caddy reverse proxies, middleware.Proxy, and safe handling of X-Forwarded-* headers.