LiveAPI Demo

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.

LiveAPI Demo

If you’re tired of updating Swagger manually or syncing Postman collections, give it a shot.

Similar Posts