Echo CSRF Protection
Introduction
Cross-Site Request Forgery (CSRF) is a type of security vulnerability that allows attackers to trick users into performing unwanted actions on websites where they're authenticated. For example, if you're logged into your bank's website and visit a malicious site, that site might trick your browser into sending a request to transfer money without your knowledge or consent.
Echo framework provides built-in CSRF protection mechanisms to prevent these attacks. In this tutorial, we'll explore how to implement CSRF protection in your Echo applications, understand how it works, and learn best practices for keeping your web applications secure.
What is CSRF?
Before diving into implementation, let's understand what CSRF is:
- CSRF exploits the trust that a website has in a user's browser
- Attacks work by including malicious links or scripts in websites, emails, or chat messages
- When clicked by an authenticated user, these execute unwanted actions on the target site
- The user's browser automatically includes authentication cookies, making the request appear legitimate
Setting Up CSRF Protection in Echo
Installing Dependencies
First, make sure your project uses the Echo framework:
go get -u github.com/labstack/echo/v4
go get -u github.com/labstack/echo/v4/middleware
Basic CSRF Implementation
Here's how to add CSRF protection 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()
// Enable CSRF middleware
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:csrf",
ContextKey: "csrf",
CookieName: "csrf",
CookiePath: "/",
CookieMaxAge: 3600, // 1 hour in seconds
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: http.SameSiteStrictMode,
}))
// Routes
e.GET("/form", showForm)
e.POST("/process", processForm)
// Start the server
e.Logger.Fatal(e.Start(":8080"))
}
func showForm(c echo.Context) error {
// Get CSRF token from the context
csrf := c.Get("csrf").(string)
// Create an HTML form with the CSRF token
form := `
<form method="POST" action="/process">
<input type="hidden" name="csrf" value="` + csrf + `">
<input type="text" name="name" placeholder="Enter your name">
<button type="submit">Submit</button>
</form>
`
return c.HTML(http.StatusOK, form)
}
func processForm(c echo.Context) error {
name := c.FormValue("name")
return c.String(http.StatusOK, "Hello, "+name+"! Your form was processed successfully.")
}
How It Works
- The
middleware.CSRF
creates a unique token for each user session - When a user visits the form page, a token is generated and included in the form
- When the form is submitted, the middleware verifies that the token is present and valid
- If the token is missing or invalid, the request is rejected with a 403 Forbidden error
Customizing CSRF Protection
Echo allows customizing your CSRF protection with various configuration options:
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
// The HTML form field name for the CSRF token
TokenLookup: "form:csrf,header:X-CSRF-Token,query:csrf",
// The context key to store the token
ContextKey: "csrf",
// The cookie settings for storing the CSRF state
CookieName: "csrf",
CookiePath: "/",
CookieDomain: "example.com",
CookieMaxAge: 3600,
CookieSecure: true,
CookieHTTPOnly: true,
CookieSameSite: http.SameSiteStrictMode,
// Custom error handler
ErrorHandler: func(c echo.Context, err error) error {
return c.JSON(http.StatusForbidden, map[string]string{
"error": "CSRF token validation failed",
})
},
}))
CSRF Token in Different Contexts
In HTML Forms
<form method="POST" action="/api/update">
<input type="hidden" name="csrf" value="{{.csrf}}">
<!-- Other form fields -->
<button type="submit">Submit</button>
</form>
In AJAX Requests
For AJAX requests, you'll typically include the CSRF token in an HTTP header:
// Get CSRF token from a meta tag or JavaScript variable
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
// Use the token in your fetch request
fetch('/api/update', {
method: 'POST',
headers: {
'X-CSRF-Token': csrfToken,
'Content-Type': 'application/json'
},
body: JSON.stringify({
// Your data here
})
})
.then(response => response.json())
.then(data => console.log(data))
.catch(error => console.error('Error:', error));
Complete Example with Templates
Here's a more comprehensive example using Go templates:
package main
import (
"html/template"
"io"
"net/http"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
)
// Template renderer
type TemplateRenderer struct {
templates *template.Template
}
func (t *TemplateRenderer) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
return t.templates.ExecuteTemplate(w, name, data)
}
func main() {
e := echo.New()
// Initialize template renderer
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer
// Add CSRF middleware
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:csrf",
ContextKey: "csrf",
}))
// Routes
e.GET("/", func(c echo.Context) error {
return c.Render(http.StatusOK, "form.html", map[string]interface{}{
"csrf": c.Get("csrf"),
})
})
e.POST("/submit", func(c echo.Context) error {
message := c.FormValue("message")
return c.Render(http.StatusOK, "result.html", map[string]interface{}{
"message": message,
"csrf": c.Get("csrf"),
})
})
e.Logger.Fatal(e.Start(":8080"))
}
templates/form.html:
<!DOCTYPE html>
<html>
<head>
<title>CSRF Protection Example</title>
</head>
<body>
<h1>Submit a Message</h1>
<form method="POST" action="/submit">
<input type="hidden" name="csrf" value="{{.csrf}}">
<textarea name="message" rows="4" cols="50" placeholder="Enter your message"></textarea>
<br>
<button type="submit">Submit</button>
</form>
</body>
</html>
templates/result.html:
<!DOCTYPE html>
<html>
<head>
<title>Message Submitted</title>
</head>
<body>
<h1>Your Message</h1>
<p>{{.message}}</p>
<a href="/">Submit another message</a>
</body>
</html>
Best Practices for CSRF Protection
-
Always use CSRF protection for non-GET requests - Especially those that change state
-
Use secure cookie settings:
- Set
CookieSecure
totrue
to ensure cookies are only sent over HTTPS - Enable
CookieHTTPOnly
to prevent JavaScript from accessing the cookie - Use
CookieSameSite: http.SameSiteStrictMode
to restrict cross-site cookie usage
- Set
-
Implement proper error handling - Provide clear but not overly revealing error messages
-
Use both form fields and headers for CSRF tokens - This makes your API usable by both browser forms and AJAX/programmatic requests
-
Set appropriate token expiration - Balance security and user experience with token lifetimes
-
Test your CSRF protection - Try to submit forms without tokens or with invalid tokens to ensure the protection is working
When to Exclude CSRF Protection
Some endpoints might not need CSRF protection:
// Create CSRF middleware instance
csrfMiddleware := middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:X-CSRF-Token",
})
// Apply it to specific routes instead of globally
e.POST("/protected/endpoint", protectedHandler, csrfMiddleware)
// Public API endpoints might not need CSRF protection if they use other authentication mechanisms
e.POST("/api/public/endpoint", publicHandler)
API endpoints that use token-based authentication like JWT might not need CSRF protection if they don't rely on cookies for authentication.
Common CSRF Issues and Solutions
Issue: Token Not Being Validated
Problem: Form submissions succeed despite incorrect or missing CSRF tokens.
Solution: Ensure the field name in your form matches the TokenLookup
configuration and that the middleware is correctly applied to your routes.
Issue: Token Mismatch Errors for Valid Users
Problem: Users receive CSRF validation errors when submitting forms legitimately.
Solution:
- Check if token expiration is too short
- Ensure cookies are properly configured
- Verify that your frontend is correctly passing the token
// Increase token lifetime
middleware.CSRFWithConfig(middleware.CSRFConfig{
CookieMaxAge: 86400, // 24 hours in seconds
})
Issue: CSRF Protection Breaking API Clients
Problem: External API clients can't access endpoints due to CSRF protection.
Solution: Consider using different authentication methods for API clients, such as API keys or JWT tokens, and exempt those routes from CSRF protection.
Summary
CSRF protection is an essential security feature for web applications that handle sensitive user data or actions. The Echo framework makes implementing CSRF protection straightforward through its middleware system.
In this tutorial, we covered:
- What CSRF attacks are and why protection is necessary
- How to implement basic CSRF protection in Echo applications
- Customizing CSRF protection with various configuration options
- Working with CSRF tokens in HTML forms and AJAX requests
- Best practices for effective CSRF protection
- Troubleshooting common CSRF-related issues
By implementing proper CSRF protection, you can significantly enhance the security of your Echo web applications and protect your users from common web vulnerabilities.
Additional Resources
Exercises
-
Basic Implementation: Create a simple Echo application with a form that includes CSRF protection.
-
Custom Error Handling: Modify the CSRF middleware to use a custom error handler that returns a user-friendly error message.
-
API + Form Support: Build an application that supports both HTML form submissions and API requests with proper CSRF protection for each.
-
CSRF Exemption: Create an application with some routes that require CSRF protection and others that don't. Implement middleware that applies CSRF selectively.
-
Testing CSRF: Write tests for your Echo application that verify CSRF protection is working correctly.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)