Skip to main content

Echo CORS Configuration

Introduction

When developing modern web applications, you will often have your frontend and backend running on different domains. For example, your frontend might be hosted at https://myapp.com while your API is at https://api.myapp.com. This separation creates security restrictions in browsers known as the Same-Origin Policy, which prevents JavaScript from making requests to a different domain than the one that served the web page.

Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to specify who can access their resources and how. In Echo framework, configuring CORS is essential for building APIs that need to be accessed by client applications from different origins.

This guide will walk you through configuring CORS in your Echo applications, from basic setup to advanced configurations.

Understanding CORS Basics

What is CORS?

CORS is an HTTP-header based mechanism that enables a server to indicate any origins (domain, scheme, or port) other than its own from which a browser should permit loading resources.

When a browser makes a cross-origin request:

  1. The browser sends an Origin header with the request
  2. The server responds with Access-Control-Allow-Origin headers
  3. The browser checks if the requesting origin is allowed

Types of CORS Requests

  1. Simple requests: GET, POST, or HEAD requests with certain content types
  2. Preflight requests: The browser sends an OPTIONS request before the actual request to check if it's safe to send

Basic CORS Configuration in Echo

Echo provides built-in middleware for handling CORS. Let's start with a basic implementation:

go
package main

import (
"net/http"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Apply CORS middleware
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourfrontend.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
}))

// Routes
e.GET("/api/users", getUsers)

// Start server
e.Logger.Fatal(e.Start(":8080"))
}

func getUsers(c echo.Context) error {
users := []string{"John", "Jane", "Bob"}
return c.JSON(http.StatusOK, users)
}

In this example, we've configured our Echo server to:

  • Allow requests only from https://yourfrontend.com
  • Accept GET, POST, PUT, and DELETE methods

Default CORS Middleware

Echo also provides a default CORS configuration which you can use for quick development:

go
// Default CORS middleware - allows all origins
e.Use(middleware.CORS())
warning

The default CORS configuration allows all origins (*), which is not recommended for production environments.

Advanced CORS Configuration

For more control over CORS behavior, Echo offers comprehensive configuration options:

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://app.example.com", "https://admin.example.com"},
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, "X-Custom-Header"},
ExposeHeaders: []string{"Content-Length", "X-Request-ID"},
AllowCredentials: true,
MaxAge: 86400, // Maximum age (in seconds) of the preflight request cache
}))

Key Configuration Options

OptionDescriptionDefault
AllowOriginsList of allowed origins[*]
AllowMethodsList of allowed HTTP methods[GET, HEAD, PUT, POST, DELETE, PATCH]
AllowHeadersList of allowed HTTP headers[]
ExposeHeadersHeaders that browsers are allowed to access[]
AllowCredentialsControls whether the browser includes credentials (cookies, auth headers)false
MaxAgeHow long preflight results should be cached (in seconds)0

Handling Dynamic Origins

In some cases, you might need to dynamically determine which origins to allow. You can use the AllowOriginFunc option for this:

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOriginFunc: func(origin string) (bool, error) {
// Check against a list of allowed domains or using a pattern
validOrigins := []string{
"https://app.example.com",
"https://admin.example.com",
// You could also allow all subdomains with regex checking
}

for _, o := range validOrigins {
if origin == o {
return true, nil
}
}
return false, nil
},
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
AllowCredentials: true,
}))

This approach gives you more flexibility in determining which origins to allow based on your own logic.

Real-World Example: API with Multiple Client Applications

Here's a practical example of an Echo API that serves multiple client applications:

go
package main

import (
"net/http"
"strings"

"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)

func main() {
e := echo.New()

// Environment-specific CORS configuration
var allowedOrigins []string
if isProd() {
// Production environment - strict origin checking
allowedOrigins = []string{
"https://main-app.example.com",
"https://admin.example.com",
"https://partner.example.com",
}
} else {
// Development environment
allowedOrigins = []string{
"http://localhost:3000",
"http://localhost:8000",
"http://127.0.0.1:3000",
}
}

// Configure CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: allowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete, http.MethodPatch},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
ExposeHeaders: []string{"X-Total-Count", "X-Pagination-Pages"},
AllowCredentials: true,
MaxAge: 3600, // 1 hour
}))

// API routes
api := e.Group("/api")
{
users := api.Group("/users")
users.GET("", listUsers)
users.POST("", createUser)
users.GET("/:id", getUser)
users.PUT("/:id", updateUser)
users.DELETE("/:id", deleteUser)
}

e.Logger.Fatal(e.Start(":8080"))
}

func isProd() bool {
// In a real-world scenario, you'd check environment variables
return false
}

// API handlers (simplified for the example)
func listUsers(c echo.Context) error {
return c.JSON(http.StatusOK, []map[string]string{
{"id": "1", "name": "John Doe"},
{"id": "2", "name": "Jane Smith"},
})
}

func createUser(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{"id": "3", "name": "New User"})
}

func getUser(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"id": id, "name": "User " + id})
}

func updateUser(c echo.Context) error {
id := c.Param("id")
return c.JSON(http.StatusOK, map[string]string{"id": id, "name": "Updated User"})
}

func deleteUser(c echo.Context) error {
return c.NoContent(http.StatusNoContent)
}

Testing Your CORS Configuration

It's important to test your CORS configuration to ensure it works correctly:

Using Curl

You can test preflight requests with curl:

bash
# Preflight request
curl -X OPTIONS https://your-api-url.com/api/resource \
-H "Origin: https://yourfrontend.com" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v

Look for the Access-Control-Allow-Origin header in the response, which should match your frontend origin.

In JavaScript

javascript
// Test from your frontend
fetch('https://your-api-url.com/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json'
},
credentials: 'include' // Include if you're using cookies/auth
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('CORS error:', error));

Common CORS Issues and Solutions

Problem: "No 'Access-Control-Allow-Origin' header is present"

Solution: Ensure your Echo server is configured to allow the specific origin from which you're making the request.

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourfrontend.com"},
}))

Problem: Credentials not being sent or received

Solution: Set AllowCredentials: true and make sure the specific origin is listed (not wildcard *).

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourfrontend.com"},
AllowCredentials: true,
}))

On the client side, include credentials: 'include' in your fetch options.

Problem: Custom headers not allowed

Solution: Include them in the AllowHeaders list:

go
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourfrontend.com"},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, "X-Custom-Header"},
}))

Security Best Practices

  1. Avoid using wildcards in production: Instead of AllowOrigins: []string{"*"}, specify exact origins.

  2. Only allow necessary methods and headers: Don't enable methods or headers you don't actually need.

  3. Be careful with AllowCredentials: When set to true, you cannot use a wildcard origin.

  4. Use environment-specific configurations: Different settings for development, staging, and production.

  5. Consider rate limiting along with CORS: Protect your API from abuse with rate limiting middleware.

Summary

CORS configuration is essential for modern web applications that separate frontend and backend across different domains. Echo framework provides flexible middleware for handling CORS with various configuration options to suit different needs.

Key points to remember:

  • Use specific origins instead of wildcards in production
  • Configure only the methods and headers your application needs
  • Be aware of the security implications of allowing credentials
  • Test your CORS configuration thoroughly
  • Use different configurations for different environments

CORS might seem complex at first, but with Echo's middleware, you can implement a secure and effective CORS policy for your API.

Additional Resources

Exercises

  1. Set up an Echo API with CORS configuration that allows requests from http://localhost:3000 and https://myapp.com.

  2. Implement dynamic origin checking that allows all subdomains of example.com.

  3. Create a middleware that logs all CORS preflight requests to help with debugging.

  4. Configure an Echo API with different CORS settings for different routes (hint: use group-specific middleware).



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)