Echo CORS Security
Introduction
Cross-Origin Resource Sharing (CORS) is a security feature implemented by browsers that restricts web pages from making requests to a domain that's different from the one that served the original web page. In modern web applications where frontend and backend often operate on different domains, understanding and properly configuring CORS is essential for security.
This guide will help you understand how CORS works in the Echo framework and how to implement it securely in your applications.
What is CORS?
CORS is a mechanism that uses additional HTTP headers to tell browsers to give a web application running at one origin, access to selected resources from a different origin. A web application executes a cross-origin HTTP request when it requests a resource that has a different origin (domain, protocol, or port) from its own.
For security reasons, browsers restrict cross-origin HTTP requests initiated from scripts. For example, XMLHttpRequest and the Fetch API follow the same-origin policy, meaning a web application can only request resources from the same origin the application was loaded from unless proper CORS headers are used.
Why CORS is Important for Security
Without CORS:
- Malicious websites could make requests to your API on behalf of authenticated users
- Private data could be exposed to unauthorized domains
- Cross-Site Request Forgery (CSRF) attacks become easier to execute
With proper CORS configuration, you control which domains can interact with your API, reducing these security risks.
Implementing CORS in Echo
Echo provides built-in middleware to handle CORS. Here's how to implement it:
Basic CORS Setup
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.CORS())
e.GET("/api/data", func(c echo.Context) error {
return c.String(http.StatusOK, "This endpoint is accessible via CORS")
})
e.Logger.Fatal(e.Start(":8080"))
}
This basic setup allows all origins to access your API endpoints, which is not recommended for production. Let's look at a more secure configuration.
Configuring CORS Securely
package main
import (
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Configure CORS with specific settings
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"https://yourfrontend.com", "https://www.yourfrontend.com"},
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization},
AllowCredentials: true,
MaxAge: 86400, // Maximum cache time for preflight requests (in seconds)
}))
e.GET("/api/data", func(c echo.Context) error {
return c.String(http.StatusOK, "This endpoint is accessible only from allowed origins")
})
e.Logger.Fatal(e.Start(":8080"))
}
Let's break down the configuration options:
- AllowOrigins: Specifies which origins are permitted to access your API resources. Always use specific domains rather than wildcards (
*
) in production. - AllowMethods: Controls which HTTP methods are allowed when accessing the resource.
- AllowHeaders: Indicates which headers can be used when making the actual request.
- AllowCredentials: Tells browsers whether to expose the response to the frontend JavaScript code when the request's credentials mode is "include" (necessary for cookies, authorization headers, etc.).
- MaxAge: How long the results of a preflight request can be cached.
Testing CORS Configuration
Here's how you can test if your CORS configuration is working correctly:
- Run your Echo server with the CORS configuration
- Use curl or another tool to make a preflight request:
curl -X OPTIONS \
-H "Origin: https://yourfrontend.com" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Content-Type" \
-v \
http://localhost:8080/api/data
The response headers should include:
Access-Control-Allow-Origin: https://yourfrontend.com
Access-Control-Allow-Methods: GET,PUT,POST,DELETE
Access-Control-Allow-Headers: Origin,Content-Type,Accept,Authorization
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 86400
Common CORS Security Pitfalls
1. Using Wildcards in Production
// DON'T DO THIS IN PRODUCTION
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
// Other settings...
}))
This allows any website to make requests to your API, which defeats the purpose of CORS security.
2. Allowing Credentials with Wildcard Origins
// THIS WON'T WORK AND IS INSECURE
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{"*"},
AllowCredentials: true,
// Other settings...
}))
Browsers will reject this configuration as it's a security risk. If you need to allow credentials, you must specify explicit origins.
3. Not Validating Origins Dynamically
Sometimes you need to validate origins dynamically rather than using a static list:
package main
import (
"net/http"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Custom CORS middleware with dynamic origin checking
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOriginFunc: func(origin string) (bool, error) {
// Check if the origin is allowed
allowedOrigins := []string{
"https://yourfrontend.com",
"https://staging.yourfrontend.com",
}
for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin || strings.HasSuffix(origin, ".yourfrontend.com") {
return true, nil
}
}
return false, nil
},
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept},
AllowCredentials: true,
MaxAge: 86400,
}))
// 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 data is secured by proper CORS",
})
}
Real-World Example: Multi-Environment CORS Configuration
Here's a more complete example showing how to configure CORS for different environments (development, staging, and production):
package main
import (
"net/http"
"os"
"strings"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
func main() {
e := echo.New()
// Get environment
env := os.Getenv("APP_ENV")
if env == "" {
env = "development" // Default to development if not specified
}
// Configure CORS based on environment
configureCORS(e, env)
// Routes
e.GET("/api/data", getData)
port := os.Getenv("PORT")
if port == "" {
port = "8080"
}
e.Logger.Fatal(e.Start(":" + port))
}
func configureCORS(e *echo.Echo, env string) {
var allowedOrigins []string
switch env {
case "production":
allowedOrigins = []string{
"https://app.example.com",
"https://api.example.com",
}
case "staging":
allowedOrigins = []string{
"https://staging.example.com",
"https://api-staging.example.com",
}
default: // development
allowedOrigins = []string{
"http://localhost:3000",
"http://127.0.0.1:3000",
}
}
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: allowedOrigins,
AllowMethods: []string{http.MethodGet, http.MethodPut, http.MethodPost, http.MethodDelete, http.MethodPatch},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization, "X-App-Token"},
AllowCredentials: true,
MaxAge: 86400,
}))
// Add CORS debug logging in development
if env == "development" {
e.Use(func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
req := c.Request()
res := c.Response()
if origin := req.Header.Get(echo.HeaderOrigin); origin != "" {
e.Logger.Infof("CORS Request - Origin: %s, Method: %s, Path: %s",
origin, req.Method, req.URL.Path)
for _, allowedOrigin := range allowedOrigins {
if origin == allowedOrigin {
e.Logger.Info("CORS Origin Allowed")
break
}
}
}
return next(c)
}
})
}
}
func getData(c echo.Context) error {
return c.JSON(http.StatusOK, map[string]string{
"message": "Data retrieved successfully",
"status": "success",
})
}
Summary
Properly configured CORS is essential for web security when your frontend and backend are on different origins. Key points to remember:
- CORS is a browser security feature that restricts cross-origin requests
- Echo provides built-in middleware to easily configure CORS
- Always specify allowed origins explicitly in production environments
- Be careful with
AllowCredentials: true
as it requires specific origin settings - Consider environment-specific CORS configurations
- Test your CORS setup thoroughly to ensure it works as expected
By following these guidelines, you can create secure Echo applications that safely communicate with frontends on different origins while maintaining strong security boundaries.
Additional Resources
Exercises
- Configure CORS in an Echo application to allow requests only from
https://myapp.com
and its subdomains. - Create a middleware that logs all rejected CORS requests for debugging purposes.
- Implement dynamic CORS that reads allowed origins from a configuration file or database.
- Build a small Echo server and a separate frontend application that communicate securely using proper CORS settings.
- Test your CORS configuration using tools like Postman or curl to verify that unauthorized origins are properly rejected.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)