Background GET before POST request?
britishben opened this issue · comments
Hi, I'm hoping you could help me with a problem I've run into when trying to use this library. I've been tasked with retrofitting CSRF protection into a static SPA application, with a JSON API, as in #40, but since it needs to be backwards-compatible I was hoping to not need any changes to my users' scripts.
The /login
endpoint is a simple web form, but the /apilogin
endpoint takes form values from a POST request, without requiring a GET first. Once logged in, users issue JSON requests to the /apidata
endpoint, using their session cookie (from gorilla/sessions
). Turning off CSRF protection for /apidata
would obviously defeat the purpose, so I'd need to have the header/cookie at that point, regardless of which login method was used.
So, my plan was, if I get a POST request to the /apilogin
endpoint, to make an additional GET (or possibly HEAD) request first to get the CSRF cookie, and then use that for the rest of the login process. I can see the GET being generated, and the cookie copied across, but I'm still getting a 403 on a POST to that endpoint.
I've made up a quick example to show what I mean (obviously, the authentication is a little bit stronger in the real program):
import (
"github.com/gorilla/csrf"
"net/http"
)
var mux = http.NewServeMux()
var CSRF = csrf.Protect(
[]byte("thirtytwo-bytes-of-fake-csrf-key"),
)
func loginHandle(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
if r.RequestURI == "/apilogin" && r.Method == http.MethodPost {
resp, _ := http.Get("http://127.0.0.1:8080/apilogin")
for _, cookie := range resp.Cookies(){
r.AddCookie(cookie)
}
}
h.ServeHTTP(w, r)
}
return http.HandlerFunc(fn)
}
func main() {
mux.HandleFunc("/", DoNothing)
mux.HandleFunc("/apidata", DoNothing)
mux.HandleFunc("/login", DoNothing)
mux.HandleFunc("/apilogin", APILogin)
http.ListenAndServe(":8080",
loginHandle(CSRF(mux)))
}
func DoNothing(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("Done"))
}
func APILogin(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodGet {
w.Write([]byte("OK"))
return
}
u := r.FormValue("username")
p := r.FormValue("password")
if u == "foo" && p == "bar" {
w.Write([]byte("Success!"))
return
}
http.Error(w, "Failure!", http.StatusUnauthorized)
}
Sorry if I've just not understood this enough - I'm still rather new to the language, but can you see a reason why that would fail? I did read your response in #40, where you recommended something similar, but I can't seem to get this working. If you're able to point me in the right direction, I'd appreciate it.
Hi Matt, did you manage to reproduce this?
As I said, it's entirely possible I've done something wrong here, but conceptually it makes sense to me.
Let me know if you've worked anything out.
-Benjamin
Closing due to inactivity. Re-open if any questions!