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/rateor 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.

 
		
 
			 
			