# How to Add Logging, CORS, and Authentication Middleware in Echo for Golang Clean Architecture

> Learn how to add logging, CORS, and authentication middleware in Echo for Golang Clean Architecture. Isolate framework concerns and streamline your router setup.

- Repository: [manato/golang-clean-architecture](https://github.com/manakuro/golang-clean-architecture)
- Tags: how-to-guide
- Published: 2026-03-06

---

**To add logging, CORS, and authentication middleware in Echo within a Clean Architecture project, register `middleware.Logger()`, `middleware.CORS()`, and `middleware.JWT()` in the infrastructure layer's router setup, specifically in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go), ensuring all framework-specific concerns remain isolated from business logic.**

The `manakuro/golang-clean-architecture` repository demonstrates how to structure a Go application using Clean Architecture principles while leveraging the Echo web framework. When adding middleware for logging, CORS, and authentication in Echo, it is critical to keep these infrastructure concerns confined to the router layer, ensuring that controllers and use cases remain framework-agnostic and testable.

## Understanding the Clean Architecture Router Setup

In this repository, the Echo router is initialized in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go). The `NewRouter` function already wires two essential middleware components that provide basic observability:

```go
e.Use(middleware.Logger())   // request logging
e.Use(middleware.Recover())  // panic recovery

```

These built-in Echo middleware functions handle request logging and panic recovery. Because this file resides in the **infrastructure layer**, it is the appropriate location to add additional cross-cutting concerns like CORS and authentication without violating the dependency rule of Clean Architecture or leaking framework details into the domain layer.

## Adding CORS Middleware in Echo

Cross-Origin Resource Sharing (CORS) is essential for browser-based clients. Echo provides the `middleware.CORS()` function, which can be applied globally in `NewRouter` immediately after the logger and recovery middleware.

```go
// ---------- CORS ----------
// Allow any origin – replace with a stricter config in production.
e.Use(middleware.CORS())

```

For production environments requiring stricter security, use `middleware.CORSWithConfig()` with a `middleware.CORSConfig` struct to specify allowed origins, methods, and headers explicitly:

```go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
    AllowOrigins: []string{"https://example.com"},
    AllowMethods: []string{echo.GET, echo.POST},
    AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization},
}))

```

## Implementing JWT Authentication Middleware

Authentication is implemented using Echo's `middleware.JWT()`. The signing secret is stored in [`config/config.yml`](https://github.com/manakuro/golang-clean-architecture/blob/main/config/config.yml) and loaded via Viper in [`pkg/config/config.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/config/config.go), ensuring secrets are not hard-coded.

First, add the JWT secret to your configuration file:

```yaml

# File: config/config.yml

jwt:
  secret: "YOUR_SUPER_SECRET_KEY"
server:
  address: ":8080"
database:
  user: "root"
  password: "password"
  net: "tcp"
  addr: "127.0.0.1:3306"
  dbname: "mydb"
  allowNativePasswords: true
  params:
    parseTime: "True"

```

The `ReadConfig` function unmarshals this file into the global `config.C` variable. Then, initialize the JWT middleware in `NewRouter` using the secret from `config.C.JWT.Secret`:

```go
// ---------- JWT Authentication ----------
jwtSecret := []byte(config.C.JWT.Secret)
authGroup := e.Group("", middleware.JWT(jwtSecret))

```

The `middleware.JWT` function validates the `Authorization: Bearer <token>` header and populates the Echo context with claims accessible via `ctx.Get("user")`.

## Protecting Routes with Authentication

In Clean Architecture, controllers should not handle authentication logic directly. Instead, use Echo's route groups to apply middleware selectively, keeping public and protected endpoints clearly separated.

Public routes remain on the root router:

```go
// Public endpoints
e.GET("/users", func(ctx echo.Context) error { return c.User.GetUsers(ctx) })
e.POST("/users", func(ctx echo.Context) error { return c.User.CreateUser(ctx) })

```

Protected routes use the `authGroup` created earlier:

```go
// Protected route example
authGroup.GET("/profile", func(ctx echo.Context) error {
    // Retrieve JWT claims injected by middleware
    claims := ctx.Get("user").(*jwt.Token).Claims.(jwt.MapClaims)
    userID := claims["sub"].(string)
    
    // Pass to controller method
    return c.User.GetProfile(ctx, userID)
})

```

Controllers receive the Echo context via the `Context` interface defined in [`pkg/adapter/controller/context.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/context.go), allowing them to retrieve user identity when necessary while remaining decoupled from the JWT implementation details.

## Summary

- **Infrastructure isolation**: All Echo middleware—including logging, CORS, and JWT authentication—belongs in [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go) to maintain Clean Architecture boundaries and prevent framework leakage into business logic.
- **Built-in observability**: The repository already implements `middleware.Logger()` and `middleware.Recover()` for request logging and panic recovery.
- **CORS configuration**: Add `middleware.CORS()` globally to handle cross-origin requests, or use `middleware.CORSWithConfig()` for production-specific origin restrictions.
- **JWT authentication**: Store the signing secret in [`config/config.yml`](https://github.com/manakuro/golang-clean-architecture/blob/main/config/config.yml), load it via `config.C.JWT.Secret`, and apply `middleware.JWT()` to route groups to protect specific endpoints while keeping public routes accessible.
- **Context propagation**: Controllers retrieve JWT claims via `ctx.Get("user")` without importing authentication logic into use-case layers, preserving testability and architectural integrity.

## Frequently Asked Questions

### Where should middleware be registered in a Clean Architecture Go project?

Middleware should be registered in the **infrastructure layer**, specifically within the router setup file at [`pkg/infrastructure/router/router.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/infrastructure/router/router.go). This ensures that framework-specific concerns like logging, CORS, and JWT validation remain isolated from domain and use-case layers, preserving the dependency rule that inner circles should not depend on external frameworks.

### How do I configure CORS for specific origins instead of allowing all?

Instead of using the permissive `middleware.CORS()`, use `middleware.CORSWithConfig()` with a `middleware.CORSConfig` struct. Specify the `AllowOrigins` slice with your trusted domains, and optionally restrict `AllowMethods` and `AllowHeaders`. This configuration should be added in `NewRouter` after the logger and recovery middlewares but before route definitions.

### Can I apply JWT authentication to only specific routes while keeping others public?

Yes. Rather than applying `middleware.JWT()` globally with `e.Use()`, create a route group using `e.Group("", middleware.JWT(jwtSecret))`. This group acts as a middleware pipeline that only applies to routes registered on that group. Public routes can remain on the root `echo.Echo` instance, allowing you to mix protected and unprotected endpoints while maintaining Clean Architecture separation.

### How does the controller access the authenticated user identity without breaking Clean Architecture?

The Echo JWT middleware stores parsed token claims in the context under the key `"user"`. Inside a controller method, retrieve the claims using `ctx.Get("user").(*jwt.Token).Claims.(jwt.MapClaims)`, then extract specific fields like the subject (`sub`) or user ID. Because the controller receives an abstraction of the context (via the `Context` interface in [`pkg/adapter/controller/context.go`](https://github.com/manakuro/golang-clean-architecture/blob/main/pkg/adapter/controller/context.go)), and the JWT logic remains in the infrastructure layer, the use-case layer remains completely decoupled from authentication concerns.