Securing REST APIs in Go (Echo Framework Edition)
Hi there! I’m Maneshwar. Right now, I’m building LiveAPI, a first-of-its-kind tool that helps you automatically index API endpoints across all your repositories. LiveAPI makes it easier to discover, understand, and interact with APIs in large infrastructures.
🔐 Securing REST APIs in Go (Echo Framework Edition)
APIs are a goldmine for attackers if not properly secured. In this guide, we’ll lock down an API built using the Echo web framework.
The focus will be on securing headers, input validation, rate limiting, and other best practices.
Project Setup
Install Echo and required dependencies:
go get github.com/labstack/echo/v4
go get github.com/labstack/echo/v4/middleware
1. Set Security Headers
Security headers protect against attacks like MIME sniffing, clickjacking, and XSS.
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
XFrameOptions: "DENY", // Clickjacking
XContentTypeOptions: "nosniff", // Prevent MIME sniffing
ContentSecurityPolicy: "default-src 'none'", // Prevent loading external scripts
}))
You can also force Content-Type
:
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Set(echo.HeaderContentType, echo.MIMEApplicationJSON)
return next(c)
}
})
2. Strip Fingerprinting Headers
Echo adds the Server
header by default. You can strip it manually:
e.HideBanner = true
e.HidePort = true
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
c.Response().Header().Del("Server")
c.Response().Header().Del("X-Powered-By")
return next(c)
}
})
3. JWT Authentication
Use github.com/golang-jwt/jwt/v5
with Echo’s JWT middleware:
go get github.com/golang-jwt/jwt/v5
import (
"github.com/labstack/echo/v4/middleware"
"github.com/golang-jwt/jwt/v5"
)
e.Use(middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte("your-secret-key"),
}))
To generate a token on login:
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"user_id": 1,
"exp": time.Now().Add(time.Hour * 1).Unix(),
})
tokenStr, _ := token.SignedString([]byte("your-secret-key"))
return c.JSON(http.StatusOK, echo.Map{"token": tokenStr})
4. Return Only Required Data
Don’t leak internal DB fields or sensitive info.
type User struct {
ID int `json:"id"`
Name string `json:"name"`
// Password omitted on purpose
}
func getUser(c echo.Context) error {
u := User{ID: 1, Name: "John"}
return c.JSON(http.StatusOK, u)
}
5. Use Proper HTTP Status Codes
Make sure each endpoint sends meaningful response codes.
if userNotFound {
return c.JSON(http.StatusNotFound, echo.Map{"error": "User not found"})
}
if !authenticated {
return c.JSON(http.StatusUnauthorized, echo.Map{"error": "Unauthorized"})
}
Status codes:
200 OK
400 Bad Request
401 Unauthorized
403 Forbidden
404 Not Found
405 Method Not Allowed
6. Rate Limiting
Echo doesn’t include native rate limiting but you can plug in something like golang.org/x/time/rate
or a third-party middleware like echo-contrib/ratelimiter.
Basic example with token bucket (in-memory):
import "golang.org/x/time/rate"
var limiter = rate.NewLimiter(1, 5) // 1 request per second, burst of 5
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if !limiter.Allow() {
return c.JSON(http.StatusTooManyRequests, echo.Map{"error": "Too Many Requests"})
}
return next(c)
}
})
7. Input Validation & Sanitization
Use github.com/go-playground/validator/v10
for input validation:
go get github.com/go-playground/validator/v10
type LoginInput struct {
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=6"`
}
func login(c echo.Context) error {
var input LoginInput
if err := c.Bind(&input); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "Invalid body"})
}
validate := validator.New()
if err := validate.Struct(input); err != nil {
return c.JSON(http.StatusBadRequest, echo.Map{"error": "Validation failed"})
}
return c.JSON(http.StatusOK, echo.Map{"message": "Logged in"})
}
8. Centralized Error Handler
Avoid leaking stack traces or sensitive errors.
e.HTTPErrorHandler = func(err error, c echo.Context) {
code := http.StatusInternalServerError
msg := "Internal Server Error"
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
msg = fmt.Sprintf("%v", he.Message)
}
c.JSON(code, echo.Map{"error": msg})
}
Final Recap
Task | Implementation |
---|---|
Set security headers | middleware.SecureWithConfig |
Strip server fingerprinting | Manual headers removal |
JWT Authentication | middleware.JWTWithConfig |
Input validation | go-playground/validator |
Proper status codes | Manual response management |
Rate limiting |
x/time/rate or contrib |
Sanitize output | Avoid returning sensitive fields |
Central error handler | Custom error function |
Ready to Deploy?
- Use HTTPS in production (TLS termination with Nginx or Caddy).
- Keep secrets and credentials out of source code.
- Regularly update dependencies.
- Monitor and audit API access.
LiveAPI helps you get all your backend APIs documented in a few minutes.
With LiveAPI, you can generate interactive API docs that allow users to search and execute endpoints directly from the browser.
If you’re tired of updating Swagger manually or syncing Postman collections, give it a shot.