Echo CORS Middleware
Introduction
When building web applications, you'll frequently encounter a security feature called Cross-Origin Resource Sharing (CORS). This feature controls how web pages in one domain can request resources from a server in a different domain. By default, web browsers block these cross-origin requests as a security measure.
Echo's CORS middleware provides a simple way to configure your server to handle cross-origin requests appropriately. This middleware is essential when you're developing APIs that need to be consumed by web applications hosted on different domains.
What is CORS?
CORS (Cross-Origin Resource Sharing) is a security mechanism implemented by browsers that restricts web pages from making requests to a domain different from the one that served the original web page.
For example:
- If your frontend application is hosted at
https://myapp.com
- And your API is hosted at
https://api.myapp.com
- The browser will block direct requests from the frontend to the API unless proper CORS headers are configured
The Echo framework provides built-in middleware to handle these scenarios elegantly.
Setting Up Echo CORS Middleware
Basic Implementation
Let's start with a basic example of how to add CORS middleware to your Echo application:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
// Create a new Echo instance
e := echo.New()
// Add CORS middleware with default configuration
e.Use(middleware.CORS())
// Define a route that will be accessible cross-origin
e.GET("/api/data", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "This data is accessible from different origins!",
})
})
// Start the server
e.Logger.Fatal(e.Start(":8080"))
}
With this configuration, your API endpoint /api/data
will be accessible from any domain, as the default CORS middleware configuration allows all origins.
Custom CORS Configuration
In real-world applications, you'll want to restrict which domains can access your API. Here's how to customize the CORS middleware:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Custom CORS configuration
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://www.mywebsite.com", "https://app.mywebsite.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
AllowCredentials: true,
MaxAge: 86400, // 24 hours
}))
// Routes
e.GET("/api/data", getData)
e.Logger.Fatal(e.Start(":8080"))
}
func getData(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "This is restricted to specific origins",
})
}
In this example, only requests from https://www.mywebsite.com
and https://app.mywebsite.com
will be allowed to access the API.
Understanding CORS Configuration Options
The middleware.CORSConfig
struct offers several options to customize your CORS behavior:
Option | Description | Default |
---|---|---|
AllowOrigins | List of allowed origins | ["*"] (all origins) |
AllowMethods | HTTP methods allowed | [GET, HEAD, PUT, POST, DELETE, PATCH] |
AllowHeaders | Headers that can be used in the request | [] |
AllowCredentials | Whether cookies can be sent in cross-origin requests | false |
ExposeHeaders | Headers that browsers are allowed to access | [] |
MaxAge | How long the results of a preflight request can be cached (in seconds) | 0 |
Practical Example: Creating a Public API with Restricted Methods
Let's build an example API that allows read access from any domain, but restricts write operations to specific origins:
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Configure CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"}, // Allow all origins for GET
AllowMethods: []string{http.MethodGet}, // Only allow GET by default
AllowHeaders: []string{echo.HeaderContentType},
}))
// Public routes (read-only)
e.GET("/api/products", getProducts)
// Restricted group with custom CORS for write operations
admin := e.Group("/api/admin")
admin.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://admin.mycompany.com"}, // Only allow admin origin
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization},
AllowCredentials: true,
}))
// Admin routes (read/write)
admin.POST("/products", createProduct)
admin.PUT("/products/:id", updateProduct)
admin.DELETE("/products/:id", deleteProduct)
e.Logger.Fatal(e.Start(":8080"))
}
func getProducts(c echo.Context) error {
products := []map[string]interface{}{
{"id": 1, "name": "Product 1", "price": 29.99},
{"id": 2, "name": "Product 2", "price": 39.99},
}
return c.JSON(http.StatusOK, products)
}
func createProduct(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{"status": "product created"})
}
func updateProduct(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "product updated"})
}
func deleteProduct(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{"status": "product deleted"})
}
This example demonstrates a common pattern where read operations are public, but write operations are restricted to specific domains.
Handling Preflight Requests
When browsers make cross-origin requests that might modify data (like POST, PUT, or DELETE), they first send a preflight OPTIONS request to check if the actual request is safe to send.
Echo's CORS middleware automatically handles these preflight requests, but it's useful to understand what's happening:
Browser -> OPTIONS /api/data -> Server
(Preflight Check)
Server responds with:
- Access-Control-Allow-Origin: https://allowed-domain.com
- Access-Control-Allow-Methods: GET, POST, PUT, DELETE
- Access-Control-Allow-Headers: Content-Type, Authorization
If preflight successful:
Browser -> POST /api/data -> Server
(Actual Request)
Common CORS Issues and Solutions
Issue 1: "No 'Access-Control-Allow-Origin' header is present"
This error occurs when the server doesn't include the necessary CORS headers.
Solution: Ensure your Echo CORS middleware is properly configured and applied to the correct routes.
Issue 2: Cookies not being sent with requests
Solution: Set AllowCredentials
to true
and specify exact origins (not wildcard *
):
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://mywebsite.com"},
AllowCredentials: true,
}))
Issue 3: Custom headers not being accepted
Solution: Add your custom headers to the AllowHeaders
list:
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://mywebsite.com"},
AllowHeaders: []string{echo.HeaderContentType, "X-Custom-Header"},
}))
Security Considerations
When implementing CORS, keep these security points in mind:
- Avoid using
AllowOrigins: []string{"*"}
in production for endpoints that modify data or return sensitive information - Be specific with allowed origins - only include domains that truly need access
- Limit allowed methods to only what's necessary for each endpoint
- Consider using environment variables for allowed origins to easily switch between development and production settings
func main() {
e := echo.New()
// Get allowed origins from environment
allowedOrigins := strings.Split(os.Getenv("ALLOWED_ORIGINS"), ",")
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: allowedOrigins,
// other options...
}))
// Routes...
}
Complete Working Example
Here's a complete working example that demonstrates different CORS configurations for different route groups:
package main
import (
"net/http"
"os"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Add logging middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
// Public API - Allow all origins, GET only
publicAPI := e.Group("/api/public")
publicAPI.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowMethods: []string{http.MethodGet},
}))
publicAPI.GET("/info", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"version": "1.0.0",
"status": "operational",
})
})
// Partner API - Allow specific origins, more methods
partnerAPI := e.Group("/api/partners")
partnerAPI.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://partner1.com", "https://partner2.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost},
AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization},
AllowCredentials: true,
}))
partnerAPI.GET("/data", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Partner data available here",
})
})
partnerAPI.POST("/feedback", func(c echo.Context) error {
return c.JSON(http.StatusCreated, map[string]string{
"message": "Feedback received",
})
})
// Internal API - Restricted to company domains
internalAPI := e.Group("/api/internal")
internalAPI.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://*.mycompany.com"},
AllowMethods: []string{http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete},
AllowHeaders: []string{echo.HeaderContentType, echo.HeaderAuthorization, "X-Company-Auth"},
ExposeHeaders: []string{"X-Response-Time"},
AllowCredentials: true,
MaxAge: 3600,
}))
internalAPI.GET("/dashboard", func(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Internal dashboard data",
})
})
// Get port from environment or default to 8080
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
e.Logger.Fatal(e.Start(":" + port))
}
This example demonstrates different CORS configurations for:
- Public API endpoints (open to all, read-only)
- Partner API endpoints (limited to specific partners)
- Internal API endpoints (restricted to company domains)
Summary
Echo's CORS middleware provides a flexible and powerful way to control cross-origin access to your API endpoints. By configuring the middleware appropriately, you can:
- Allow cross-origin requests from specific domains
- Restrict which HTTP methods are allowed for cross-origin requests
- Control which headers can be included in requests
- Determine whether credentials (cookies, HTTP authentication) can be included
- Improve performance by setting appropriate cache times for preflight requests
Understanding and properly configuring CORS is crucial for creating secure and accessible web APIs with Echo.
Additional Resources
Exercises
- Create an Echo application with two route groups: one that allows CORS from any origin and another that only allows requests from "localhost".
- Modify the complete example to add a route group for mobile apps that only accepts requests from specific mobile app domains.
- Implement CORS middleware that reads allowed origins from a configuration file.
- Create a debugging middleware that logs all CORS-related headers received and sent by your application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)