Introduction
If you’ve ever dealt with user authentication and forms on the web, you’ve probably heard of CSRF or Cross-Site Request Forgery. It’s a common security vulnerability that, if left unchecked, can allow attackers to trick users into performing unwanted actions.
In this post, we’ll demystify CSRF, understand how CSRF tokens work, and walk through a clean implementation using Go. Whether you’re building a small web app or a production-grade backend, this guide will help you secure your endpoints from this sneaky threat.
What is CSRF (Cross-Site Request Forgery)?
CSRF is a type of attack that tricks an authenticated user into performing an action they didn’t intend to. It exploits the trust a site has in the user’s browser.
Example Scenario:
- You’re logged into your bank account.
- You visit a malicious site in another tab.
-
That site secretly sends a request like
POST /transfer
to your bank. - Since you’re already authenticated (your session cookie is automatically sent), the request goes through.
Boom 💥 — money transferred without your consent.
How Do CSRF Tokens Work?
A CSRF token is a random, secret value that your server generates and attaches to every form or state-changing request. The server then validates this token with each request.
The Flow:
- Server generates a CSRF token per session.
- Token is embedded in every form or AJAX request.
- When the user submits the form, the token is sent back.
- Server checks if the token matches the one in the session.
- If valid ➝ process request. If not ➝ block it.
How to Implement CSRF Protection in Go
We’ll use Go’s standard net/http
and a handy middleware package called gorilla/csrf
to keep things simple and secure.
📦 Step 1: Install the gorilla/csrf package
bashgo get github.com/gorilla/csrf
🛠️ Step 2: Basic Setup with Middleware
gopackage main
import (
"fmt"
"net/http"
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
csrfMiddleware := csrf.Protect(
[]byte("32-byte-long-auth-key-123456789012"),
csrf.Secure(false), // Set to true in production (requires HTTPS)
)
r.HandleFunc("/form", showFormHandler)
r.HandleFunc("/submit", submitHandler).Methods("POST")
http.ListenAndServe(":8080", csrfMiddleware(r))
}
📝 Step 3: Displaying the CSRF Token in a Form
gofunc showFormHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/html")
form := fmt.Sprintf(`
<form action="/submit" method="POST">
<input type="hidden" name="%s" value="%s">
<input type="text" name="message">
<input type="submit" value="Submit">
</form>
`, csrf.TokenFieldName, csrf.Token(r))
w.Write([]byte(form))
}
csrf.Token(r)
grabs the token for the current request.csrf.TokenFieldName
gives you the token’s field name (gorilla.csrf.Token
by default).
📩 Step 4: Handling the POST Request
gofunc submitHandler(w http.ResponseWriter, r *http.Request) {
message := r.FormValue("message")
fmt.Fprintf(w, "Received: %s", message)
}
The CSRF middleware automatically checks the token. If it’s missing or invalid, it returns a 403 Forbidden
and stops execution.
Tips for Production
-
Use HTTPS: Always set
csrf.Secure(true)
in production to ensure cookies are only sent over HTTPS. -
Custom Error Handling: You can set your own error handler with
csrf.ErrorHandler
. - Token per Request: The token is typically tied to a session, but you can rotate per request by using advanced options.
When Not to Use CSRF Protection
If your app is fully stateless (e.g. uses JWT tokens and no cookies), CSRF isn’t a concern. But for any session-based login (with cookies), CSRF protection is critical.
Conclusion
CSRF tokens are a must-have security measure for any web app dealing with authenticated users. With Go, and the help of the Gorilla CSRF middleware, implementing protection is straightforward and effective.
✅ Generate a token ✅ Add it to forms ✅ Let middleware do the validation
That’s it! Now your users are safe from sneaky cross-site requests.
Album of the day: