gorilla / csrf

Package gorilla/csrf provides Cross Site Request Forgery (CSRF) prevention middleware for Go web applications & services 🔒

Home Page:https://gorilla.github.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Clearing `_gorilla_csrf` cookie not regenerating

donaldthai opened this issue · comments

Hi there, I'm having trouble with the secure cookie _gorilla_csrf regenerating when I clear out the cookie from browser. I notice it only regenerates after a POST which would obviously fail and return a 403 error. I also notice that the cookie doesn't get set on page load as well.

More context:

  • Working with a Single Page App, React
  • Using Goji for handling multiple routes
  • Passing in csrf.Protect() middleware to Goji at the root route

Any idea what's going on?

Any help would be appreciated!

See https://github.com/gorilla/csrf#javascript-applications

In an SPA you'll likely want to pre-fetch the cookie and the token behind the scenes, and then pass the token on every POST request.

Do you have any examples of pre-fetching the _gorilla_csrf cookie? The only examples I see are the ones for the masked csrf token.

Not sure what I'm doing wrong. I've set the the path to csrf.Path('/') which I would think would cover everything. Still not getting the cookie when I do a GET request for just '/'.

I do get the _gorilla_csrf cookie on a failed POST like what I mentioned before.

Make sure the Path attribute of the cookie is broad enough to be sent on all requests in return

Here's a simplified version. It's initially what I'm doing:

Server

site := goji.NewMux()
// handling static content
// Goji pat library, "goji.io/pat"
site.Handle(pat.Get("/static/*"), clientHandler)

root := goji.NewMux()
// testing on localhost
root.Use(csrf.Protect(csrfAuthKey, csrf.Secure(false), csrf.Path("/")))
// writing "masked_gorilla_csrf" cookie
root.Use(MaskedCsrfCookieMiddleware())
site.Handle(pat.New("/*"), root)

// using "net/http" package
server := &http.Server{
	Addr:           addr("locahost", 8080),
	Handler:        site,
}

// go routine
go func() {
         server.ListenAndServe()
}()

Middleware to write the masked token to cookie

func WriteMaskedCsrfCookie(w http.ResponseWriter, csrfToken string) {

	// New cookie
	cookie := http.Cookie{
		Name:    "masked_gorilla_csrf",
		Value:   "blank",
		Path:    "/",
		Expires: time.Unix(0, 0),
		MaxAge:  -1,
	}
        // in mins
	expiry := 15
	maxAge := 15 * 60

	cookie.Value = csrfToken
	cookie.Expires = expiry
	cookie.MaxAge = maxAge

	w.Header().Set("Set-Cookie", cookie.String())
}

func MaskedCsrfCookieMiddleware() func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		mw := func(w http.ResponseWriter, r *http.Request) {
			WriteMaskedCsrfCookie(w, csrf.Token(r))
			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(mw)
	}
}

Tried both browser navigation to "localhost:8080" and javascript fetch:

Fetch code

fetch("http://localhost:8080/", {
    "method": "GET",
});

Currently, I'm writing another cookie named masked_gorilla_csrf to pass back the masked token from csrf.Token(). I then retrieve that cookie and feed it into all of my requests by setting the X-CSRF-Token request header.

No issues getting the masked token. Just having trouble with the _gorilla_csrf cookie.

GET request
For this one, I already had the masked_gorilla_csrf from a previous GET request.

GET / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36
DNT: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: masked_gorilla_csrf=/mXggd3LDD9Nb+DqP77i2xPel/2bcHIjfUMciGVfJShAw0Apn2pvxkrpHU9g79XFIDBbqA/SMQUzv6h/LyWuWg==

GET Response:

HTTP/1.1 200 OK
Accept-Ranges: bytes
Content-Length: 604
Content-Security-Policy: frame-ancestors 'none'
Content-Type: text/html; charset=utf-8
Last-Modified: Tue, 11 Dec 2018 02:51:27 GMT
Set-Cookie: masked_gorilla_csrf=U/JffY5lf1AJJnSvhMF8bguwm8Xf8g0/4gmslRGDQXj6IzeJgUmdVMSCqSFTwvnq9h1LGsyW79BW8xTB4hCf3A==; Path=/; Expires=Fri, 01 Jan 9999 12:00:00 GMT; Max-Age=99999999
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
Vary: Cookie
X-Frame-Options: deny
X-Xss-Protection: 1; mode=block
Date: Tue, 11 Dec 2018 19:04:47 GMT

POST Request

POST / HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Content-Length: 0
Pragma: no-cache
Cache-Control: no-cache
Origin: http://localhost:8080
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.80 Safari/537.36
DNT: 1
Accept: */*
Referer: http://localhost:8080/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Cookie: masked_gorilla_csrf=Z/tAJoao4DCNLwbchoheqD+LTqTeJiV2dIMyyOcQ5s40AxCeW4Izs3EOXuaEUTzNWT5kirqXaDeu9KIxISbF2Q==

POST response

HTTP/1.1 403 Forbidden
Content-Security-Policy: frame-ancestors 'none'
Content-Type: text/plain; charset=utf-8
Set-Cookie: session_token=blank; Path=/; Expires=Thu, 01 Jan 1970 00:00:00 GMT; Max-Age=0
Set-Cookie: _gorilla_csrf=MTU0NDU1NTIxNnxJbFZoYmxabk1sTjBkMWR3ZFhwcVpFWk9PVlk1VVZOcU5ESnBkbkpoZVhkTGEwbFRjME55ZG5wUE1UQTlJZ289fPJs9mjuG4KS8ARaoeRdVQatoXGG_ABfhcigcg8QZLj9; Path=/; HttpOnly
Strict-Transport-Security: max-age=31536000; includeSubdomains; preload
X-Content-Type-Options: nosniff
X-Frame-Options: deny
X-Xss-Protection: 1; mode=block
Date: Tue, 11 Dec 2018 19:06:56 GMT
Content-Length: 31

Quick note, I just made a POST request to the "/" that hasn't been setup for a POST.

Hey! Just a quick update. Was able to figure out what was wrong. Had to add the CSRF middleware before writing the masked_gorilla_cookie and set it after it.

Updated Server

site := goji.NewMux()
// handling static content
// Goji pat library, "goji.io/pat"
site.Handle(pat.Get("/static/*"), clientHandler)

root := goji.NewMux()
// testing on localhost
UnmaskedCsrfCookieMiddleware := csrf.Protect(csrfAuthKey, csrf.Secure(false), csrf.Path("/"))
root.Use(UnmaskedCsrfCookieMiddleware)
// writing "masked_gorilla_csrf" cookie
root.Use(MaskedCsrfCookieMiddleware())
root.Use(UnmaskedCsrfCookieMiddleware)
site.Handle(pat.New("/*"), root)

// using "net/http" package
server := &http.Server{
	Addr:           addr("locahost", 8080),
	Handler:        site,
}

// go routine
go func() {
         server.ListenAndServe()
}()

Another update! A colleague figured out that the _gorilla_csrf cookie was being cleared when we set our masked_gorilla_csrf cookie. This was because we were using w.Header().Set("Set-Cookie", cookie.String()) cleared all the cookies. Needed to use http.SetCookie which uses w.Header().Add() under the hood.

Updated code

Server

site := goji.NewMux()
// handling static content
// Goji pat library, "goji.io/pat"
site.Handle(pat.Get("/static/*"), clientHandler)

root := goji.NewMux()
// testing on localhost
root.Use(csrf.Protect(csrfAuthKey, csrf.Secure(false), csrf.Path("/")))
// writing "masked_gorilla_csrf" cookie
root.Use(MaskedCsrfCookieMiddleware())
site.Handle(pat.New("/*"), root)

// using "net/http" package
server := &http.Server{
	Addr:           addr("locahost", 8080),
	Handler:        site,
}

// go routine
go func() {
         server.ListenAndServe()
}()

Middleware to write the masked token to cookie

func WriteMaskedCsrfCookie(w http.ResponseWriter, csrfToken string) {

	// New cookie
	cookie := http.Cookie{
		Name:    "masked_gorilla_csrf",
		Value:   "blank",
		Path:    "/",
		Expires: time.Unix(0, 0),
		MaxAge:  -1,
	}
        // in mins
	expiry := 15
	maxAge := 15 * 60

	cookie.Value = csrfToken
	cookie.Expires = expiry
	cookie.MaxAge = maxAge

	http.SetCookie(w, &cookie)
}

func MaskedCsrfCookieMiddleware() func(next http.Handler) http.Handler {
	return func(next http.Handler) http.Handler {
		mw := func(w http.ResponseWriter, r *http.Request) {
			WriteMaskedCsrfCookie(w, csrf.Token(r))
			next.ServeHTTP(w, r)
		}
		return http.HandlerFunc(mw)
	}
}
commented

This issue has been automatically marked as stale because it hasn't seen a recent update. It'll be automatically closed in a few days.