Cross-Site Request Forgery (CSRF) is a vulnerability that allows an attacker to trick an authenticated user into performing unwanted actions on a web application. In this article, we will see how to implement simple CSRF protection from scratch in Go.
1. Basic Concept
The principle is simple: for each state-changing request (POST, PUT, DELETE), a CSRF token is required that must be sent by the user and verified on the server.
2. Token Generation
The CSRF token is generated and stored in the user's session. It is also included in HTML forms.
func generateCSRFToken() (string, error) {
b := make([]byte, 32)
if _, err := rand.Read(b); err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(b), nil
}
3. Adding the Token to the Session
When a new session is created or a protected page is accessed, the CSRF token is generated if it doesn't already exist.
func ensureCSRFToken(w http.ResponseWriter, r *http.Request) string {
session, _ := store.Get(r, "session-name")
token, ok := session.Values["csrf_token"].(string)
if !ok || token == "" {
token, _ = generateCSRFToken()
session.Values["csrf_token"] = token
session.Save(r, w)
}
return token
}
4. Including the Token in the HTML Form
The CSRF token must be included as a hidden field in every HTML form.
<form method="POST" action="/submit">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<input type="text" name="message">
<button type="submit">Send</button>
</form>
5. Server-Side Token Validation
When the request is received, the token sent is compared with the one stored in the session.
func validateCSRFToken(r *http.Request) bool {
session, _ := store.Get(r, "session-name")
storedToken, _ := session.Values["csrf_token"].(string)
submittedToken := r.FormValue("csrf_token")
return storedToken != "" && submittedToken == storedToken
}
6. Practical Application
Inside your POST handler, validate the token before proceeding:
func handleSubmit(w http.ResponseWriter, r *http.Request) {
if !validateCSRFToken(r) {
http.Error(w, "Invalid CSRF token", http.StatusForbidden)
return
}
// continue processing the request
}
Conclusion
We have seen how to build a simple yet effective CSRF protection in Go. This approach can be further improved using "double submit" tokens or by integrating with more advanced session libraries.