Skip to main content

Echo CSRF Middleware

Introduction

Cross-Site Request Forgery (CSRF) is a type of attack that forces authenticated users to submit unwanted requests to web applications. These attacks can lead to unauthorized actions being performed on behalf of users without their consent. Echo's CSRF middleware helps protect your web application from these attacks by validating that requests originate from your application and not from malicious sources.

In this tutorial, we'll explore:

  • What CSRF attacks are and why they're dangerous
  • How Echo's CSRF middleware works
  • How to implement CSRF protection in your Echo application
  • Best practices for CSRF protection

Understanding CSRF Attacks

Before diving into the middleware implementation, let's understand what CSRF attacks are and why they're concerning.

What is a CSRF Attack?

A CSRF attack occurs when a malicious website tricks a user's browser into making requests to another site where the user is already authenticated. For example:

  1. You log into your banking website and get authenticated (with cookies)
  2. Without logging out, you visit a malicious website
  3. The malicious site contains code that sends a request to your bank to transfer money
  4. Your browser automatically includes your authentication cookies with the request
  5. The bank processes the request because it appears to come from you

Why CSRF Protection is Important

CSRF attacks can lead to:

  • Unauthorized financial transactions
  • Changed user account details
  • Data theft or modification
  • Compromised account security

Echo's CSRF Middleware

Echo provides built-in CSRF middleware that helps protect your application by:

  1. Generating unique tokens for each user session
  2. Including these tokens in forms or headers
  3. Validating the token on subsequent requests

Setting Up CSRF Protection

Step 1: Import the Required Packages

go
package main

import (
"net/http"

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

Step 2: Configure the CSRF Middleware

go
func main() {
// Create a new Echo instance
e := echo.New()

// Configure CSRF middleware
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:csrf",
CookieName: "csrf",
CookieMaxAge: 3600, // 1 hour in seconds
CookieSecure: true,
CookieHTTPOnly: true,
}))

// Routes
e.GET("/form", showForm)
e.POST("/process", processForm)

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

In the configuration:

  • TokenLookup specifies where to look for the CSRF token (in this case, a form field named "csrf")
  • CookieName sets the name of the cookie that stores the CSRF token
  • CookieMaxAge determines how long the cookie is valid
  • CookieSecure ensures the cookie is only sent over HTTPS
  • CookieHTTPOnly prevents JavaScript from accessing the cookie

Step 3: Implement Form Handling with CSRF Protection

go
func showForm(c echo.Context) error {
// Get CSRF token
token := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)

// Create a form with the CSRF token
form := `
<form method="POST" action="/process">
<input type="hidden" name="csrf" value="` + token + `">
<input type="text" name="name" placeholder="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, "Form processed successfully! Hello, "+name)
}

When a user visits /form, the server generates a CSRF token and includes it in the rendered form. When the form is submitted to /process, the middleware validates the token before allowing the request to proceed.

Testing CSRF Protection

To see CSRF protection in action, try the following:

  1. Access the form page at /form
  2. Inspect the page and note the CSRF token
  3. Submit the form normally (should work)
  4. Try to submit a form without a CSRF token or with an invalid token (should fail)

For example, if you try to send a POST request without the CSRF token:

$ curl -X POST http://localhost:8080/process -d "name=John"

You'll receive an error response indicating CSRF verification failed.

Advanced CSRF Configuration

Custom Error Handling

You can customize how CSRF errors are handled:

go
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:csrf",
CookieName: "csrf",
ErrorHandler: func(err error, c echo.Context) error {
return c.HTML(http.StatusForbidden, "CSRF verification failed. Please try again.")
},
}))

Custom Token Extractor

If you need to extract the token from a different location:

go
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:X-CSRF-Token,form:csrf", // Check header first, then form
CookieName: "csrf",
}))

AJAX Requests with CSRF Protection

For AJAX requests, you typically pass the CSRF token in a header. Here's a frontend JavaScript example:

javascript
// Get the CSRF token from a meta tag or directly from the cookie
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');

// Make an AJAX request with the token
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify({ key: 'value' })
})
.then(response => response.json())
.then(data => console.log(data));

On the server side, configure the middleware to check for the token in the header:

go
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "header:X-CSRF-Token",
CookieName: "csrf",
}))

Real-World Example: User Profile Update Form

Here's a comprehensive example of protecting a user profile update form with CSRF:

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()

// Configure renderer
renderer := &TemplateRenderer{
templates: template.Must(template.ParseGlob("templates/*.html")),
}
e.Renderer = renderer

// Middleware
e.Use(middleware.Logger())
e.Use(middleware.Recover())
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
TokenLookup: "form:_csrf",
CookieName: "_csrf",
CookieMaxAge: 3600,
CookieSecure: true,
CookieHTTPOnly: true,
}))

// Routes
e.GET("/profile", showProfileForm)
e.POST("/profile/update", updateProfile)

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

func showProfileForm(c echo.Context) error {
csrfToken := c.Get(middleware.DefaultCSRFConfig.ContextKey).(string)

data := map[string]interface{}{
"csrf": csrfToken,
"username": "john_doe",
"email": "[email protected]",
}

return c.Render(http.StatusOK, "profile.html", data)
}

func updateProfile(c echo.Context) error {
username := c.FormValue("username")
email := c.FormValue("email")

// Here you would update the user's profile in a database

return c.HTML(http.StatusOK, "<p>Profile updated successfully!</p><p>Username: "+username+"</p><p>Email: "+email+"</p>")
}

And the corresponding profile.html template:

html
<!-- templates/profile.html -->
<!DOCTYPE html>
<html>
<head>
<title>Update Profile</title>
</head>
<body>
<h1>Update Your Profile</h1>
<form method="POST" action="/profile/update">
<input type="hidden" name="_csrf" value="{{.csrf}}">

<div>
<label for="username">Username:</label>
<input type="text" id="username" name="username" value="{{.username}}">
</div>

<div>
<label for="email">Email:</label>
<input type="email" id="email" name="email" value="{{.email}}">
</div>

<button type="submit">Update Profile</button>
</form>
</body>
</html>

Best Practices for CSRF Protection

  1. Always use CSRF protection for non-GET requests that change state or perform actions
  2. Enable secure and HTTP-only flags on CSRF cookies to prevent XSS attacks from stealing tokens
  3. Set appropriate cookie expiration times to balance security and user experience
  4. Use HTTPS for all traffic to prevent token theft
  5. Test your CSRF protection regularly to ensure it's working correctly
  6. Include CSRF tokens in all forms, not just those that seem security-sensitive
  7. Don't disable CSRF protection for AJAX requests—use header-based tokens instead

Common CSRF Middleware Issues and Solutions

IssueSolution
Token mismatch errorsEnsure token is correctly included in forms or headers
Token expirationConsider extending token lifetime or providing a way for users to refresh
Single-page applicationsConfigure the middleware to support AJAX by checking headers
Testing difficultiesCreate test helpers that automatically handle CSRF tokens

Summary

CSRF attacks pose a significant security risk to web applications, but Echo's CSRF middleware provides an effective defense. By implementing this middleware and following best practices, you can protect your users from malicious cross-site requests.

Remember these key points:

  • CSRF attacks trick authenticated users into making unwanted requests
  • Echo's CSRF middleware generates and validates tokens to ensure request authenticity
  • Always include CSRF protection for state-changing operations
  • Configure the middleware according to your application's specific needs

Additional Resources

Exercises

  1. Implement CSRF protection for a login form in an Echo application.
  2. Create a simple API with Echo that accepts POST requests and requires CSRF tokens in headers.
  3. Build a file upload function with CSRF protection.
  4. Implement custom error handling for CSRF failures that redirects users to a specific error page.
  5. Create a SPA that correctly handles CSRF tokens for API requests.

By implementing proper CSRF protection, you'll significantly enhance the security of your Echo web applications and protect your users from this common attack vector.



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