Skip to main content

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:

bash
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:

go
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

  1. The middleware.CSRF creates a unique token for each user session
  2. When a user visits the form page, a token is generated and included in the form
  3. When the form is submitted, the middleware verifies that the token is present and valid
  4. 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:

go
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

html
<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:

javascript
// 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:

go
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:

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:

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

  1. Always use CSRF protection for non-GET requests - Especially those that change state

  2. Use secure cookie settings:

    • Set CookieSecure to true 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
  3. Implement proper error handling - Provide clear but not overly revealing error messages

  4. Use both form fields and headers for CSRF tokens - This makes your API usable by both browser forms and AJAX/programmatic requests

  5. Set appropriate token expiration - Balance security and user experience with token lifetimes

  6. 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:

go
// 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
go
// 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

  1. Basic Implementation: Create a simple Echo application with a form that includes CSRF protection.

  2. Custom Error Handling: Modify the CSRF middleware to use a custom error handler that returns a user-friendly error message.

  3. API + Form Support: Build an application that supports both HTML form submissions and API requests with proper CSRF protection for each.

  4. CSRF Exemption: Create an application with some routes that require CSRF protection and others that don't. Implement middleware that applies CSRF selectively.

  5. 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! :)