// Protect is HTTP middleware that provides Cross-Site Request Forgery // protection. // // It securely generates a masked (unique-per-request) token that // can be embedded in the HTTP response (e.g. form field or HTTP header). // The original (unmasked) token is stored in the session, which is inaccessible // by an attacker (provided you are using HTTPS). Subsequent requests are // expected to include this token, which is compared against the session token. // Requests that do not provide a matching token are served with a HTTP 403 // 'Forbidden' error response. // // Example: // package main // // import ( // "github.com/elithrar/protect" // "github.com/gorilla/mux" // ) // // func main() { // r := mux.NewRouter() // // mux.HandlerFunc("/signup", GetSignupForm) // // POST requests without a valid token will return a HTTP 403 Forbidden. // mux.HandlerFunc("/signup/post", PostSignupForm) // // // Add the middleware to your router. // http.ListenAndServe(":8000", // // Note that the authentication key provided should be 32 bytes // // long and persist across application restarts. // csrf.Protect([]byte("32-byte-long-auth-key"))(r)) // } // // func GetSignupForm(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 Protect(authKey []byte, opts ...Option) func(http.Handler) http.Handler { return func(h http.Handler) http.Handler { cs := parseOptions(h, opts...) // Set the defaults if no options have been specified if cs.opts.ErrorHandler == nil { cs.opts.ErrorHandler = http.HandlerFunc(unauthorizedHandler) } if cs.opts.MaxAge < 1 { // Default of 12 hours cs.opts.MaxAge = defaultAge } if cs.opts.FieldName == "" { cs.opts.FieldName = fieldName } if cs.opts.CookieName == "" { cs.opts.CookieName = cookieName } if cs.opts.RequestHeader == "" { cs.opts.RequestHeader = headerName } // Create an authenticated securecookie instance. if cs.sc == nil { cs.sc = securecookie.New(authKey, nil) // Use JSON serialization (faster than one-off gob encoding) cs.sc.SetSerializer(securecookie.JSONEncoder{}) // Set the MaxAge of the underlying securecookie. cs.sc.MaxAge(cs.opts.MaxAge) } if cs.st == nil { // Default to the cookieStore cs.st = &cookieStore{ name: cs.opts.CookieName, maxAge: cs.opts.MaxAge, secure: cs.opts.Secure, httpOnly: cs.opts.HttpOnly, path: cs.opts.Path, domain: cs.opts.Domain, sc: cs.sc, } } return cs } }
// TestCookieEncode tests that an invalid cookie store returns an encoding error. func TestCookieEncode(t *testing.T) { var age = 3600 // Test with a nil hash key sc := securecookie.New(nil, nil) sc.MaxAge(age) st := &cookieStore{cookieName, age, true, true, "", "", sc} rr := httptest.NewRecorder() err := st.Save(nil, rr) if err == nil { t.Fatal("cookiestore did not report an invalid hashkey on encode") } }
// TestCookieDecode tests that an invalid cookie store returns a decoding error. func TestCookieDecode(t *testing.T) { r, err := http.NewRequest("GET", "/", nil) if err != nil { t.Fatal(err) } var age = 3600 // Test with a nil hash key sc := securecookie.New(nil, nil) sc.MaxAge(age) st := &cookieStore{cookieName, age, true, true, "", "", sc} // Set a fake cookie value so r.Cookie passes. r.Header.Set("Cookie", fmt.Sprintf("%s=%s", cookieName, "notacookie")) _, err = st.Get(r) if err == nil { t.Fatal("cookiestore did not report an invalid hashkey on decode") } }