gorilla/csrf is a HTTP middleware library that provides cross-site request forgery (CSRF) protection. It includes:
- The
csrf.Protect
middleware/handler provides CSRF protection on routes attached to a router or a sub-router. - A
csrf.Token
function that provides the token to pass into your response, whether that be a HTML form or a JSON response body. - ... and a
csrf.TemplateField
helper that you can pass into yourhtml/template
templates to replace a{{ .csrfField }}
template tag with a hidden input field.
gorilla/csrf is designed to work with any Go web framework, including:
- The Gorilla toolkit
- Go's built-in net/http package
- Goji - see the tailored fork
- Gin
- Echo
- ... and any other router/framework that rallies around Go's
http.Handler
interface.
gorilla/csrf is also compatible with middleware 'helper' libraries like Alice and Negroni.
With a properly configured Go toolchain:
go get github.com/gorilla/csrf
gorilla/csrf is easy to use: add the middleware to individual handlers with the below:
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
http.HandlerFunc("/route", CSRF(YourHandler))
... and then collect the token with csrf.Token(r)
before passing it to the
template, JSON body or HTTP header (you pick!). gorilla/csrf inspects the form body
(first) and HTTP headers (second) on subsequent POST/PUT/PATCH/DELETE/etc. requests
for the token.
Here's the common use-case: HTML forms you want to provide CSRF protection for, in order to protect malicious POST requests being made:
package main
import (
"net/http"
"github.com/gorilla/csrf"
)
func main() {
r := mux.NewRouter()
r.HandleFunc("/signup", ShowSignupForm)
// All POST requests without a valid token will return HTTP 403 Forbidden.
r.HandleFunc("/signup/post", SubmitSignupForm)
// Add the middleware to your router by wrapping it.
http.ListenAndServe(":8000",
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
}
func ShowSignupForm(w http.ResponseWriter, r *http.Request) {
// signup_form.tmpl just needs a {{ .csrfField }} template tag for
// csrf.TemplateField to inject the CSRF token into. Easy!
t.ExecuteTemplate(w, "signup_form.tmpl", map[string]interface{
csrf.TemplateTag: csrf.TemplateField(r),
})
// We could also retrieve the token directly from csrf.Token(r) and
// set it in the request header - w.Header.Set("X-CSRF-Token", token)
// This is useful if your sending JSON to clients or a front-end JavaScript
// framework.
}
func SubmitSignupForm(w http.ResponseWriter, r *http.Request) {
// We can trust that requests making it this far have satisfied
// our CSRF protection requirements.
}
This approach is useful if you're using a front-end JavaScript framework like Ember or Angular, or are providing a JSON API.
We'll also look at applying selective CSRF protection using gorilla/mux's sub-routers, as we don't handle any POST/PUT/DELETE requests with our top-level router.
package main
import (
"github.com/gorilla/csrf"
"github.com/gorilla/mux"
)
func main() {
r := mux.NewRouter()
api := r.PathPrefix("/api").Subrouter()
api.HandleFunc("/user/:id", GetUser).Methods("GET")
http.ListenAndServe(":8000",
csrf.Protect([]byte("32-byte-long-auth-key"))(r))
}
func GetUser(w http.ResponseWriter, r *http.Request) {
// Authenticate the request, get the id from the route params,
// and fetch the user from the DB, etc.
// Get the token and pass it in the CSRF header. Our JSON-speaking client
// or JavaScript framework can now read the header and return the token in
// in its own "X-CSRF-Token" request header on the subsequent POST.
w.Header().Set("X-CSRF-Token", csrf.Token(r))
b, err := json.Marshal(user)
if err != nil {
http.Error(w, err.Error(), 500)
return
}
w.Write(b)
}
What about providing your own error handler and changing the HTTP header the package inspects on requests? (i.e. an existing API you're porting to Go). Well, gorilla/csrf provides options for changing these as you see fit:
func main() {
CSRF := csrf.Protect(
[]byte("a-32-byte-long-key-goes-here"),
csrf.RequestHeader("Authenticity-Token"),
csrf.FieldName("authenticity_token"),
csrf.ErrorHandler(http.HandlerFunc(serverError(403))),
)
r := mux.NewRouter()
r.HandleFunc("/signup", GetSignupForm)
r.HandleFunc("/signup/post", PostSignupForm)
http.ListenAndServe(":8000", CSRF(r))
}
Not too bad, right?
If there's something you're confused about or a feature you would like to see added, open an issue.
Getting CSRF protection right is important, so here's some background:
- This library generates unique-per-request (masked) tokens as a mitigation against the BREACH attack.
- The 'base' (unmasked) token is stored in the session, which means that multiple browser tabs won't cause a user problems as their per-request token is compared with the base token.
- Operates on a "whitelist only" approach where safe (non-mutating) HTTP methods (GET, HEAD, OPTIONS, TRACE) are the only methods where token validation is not enforced.
- The design is based on the battle-tested Django and Ruby on Rails approaches.
- Cookies are authenticated and based on the securecookie library. They're also Secure (issued over HTTPS only) and are HttpOnly by default, because sane defaults are important.
- Go's
crypto/rand
library is used to generate the 32 byte (256 bit) tokens and the one-time-pad used for masking them.
This library does not seek to be adventurous.
BSD licensed. See the LICENSE file for details.