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

Cannot get basic version of in-browser Javascript application documentation working

francoposa opened this issue · comments

I cannot get any version of the documentation working with in-browser Javascript.

Everything works fine with curl/Postman. Browser javascript involves introducing a CORS library, but I do not think that is the problem, since after the CORS is introduced Postman workflow still works and the CORS library debug output does not show any issues.

Here is my code, stripped down as much as possible. I use Chi router because it is what I am familiar with, but that difference from the docs should not matter here.

go.mod:

go 1.17

require (
	github.com/go-chi/chi v1.5.4
	github.com/gorilla/csrf v1.7.1
	github.com/rs/cors v1.8.0
)

require (
	github.com/gorilla/securecookie v1.1.1 // indirect
	github.com/pkg/errors v0.9.1 // indirect
)

Go Code:

package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"github.com/go-chi/chi"
	"github.com/go-chi/chi/middleware"
	"github.com/gorilla/csrf"
	"github.com/rs/cors"
)

func main() {
	router := chi.NewRouter()

	// Suggested basic middleware stack from chi's docs
	router.Use(middleware.RequestID)
	router.Use(middleware.RealIP)
	router.Use(middleware.Logger)
	router.Use(middleware.Recoverer)

	corsMiddleware := cors.New(cors.Options{
		AllowOriginFunc: func(origin string) bool {return true},
		AllowCredentials: true,
		AllowedHeaders:   []string{"Content-Type", "X-CSRF-Token"},
		ExposedHeaders:   []string{"Content-Type", "X-CSRF-Token"},
		Debug:            true,
	}).Handler

	router.Use(corsMiddleware)

	CSRF := csrf.Protect(
		[]byte("place-your-32-byte-long-key-here"),
		csrf.RequestHeader("X-CSRF-Token"),
		csrf.Secure(false),
		csrf.TrustedOrigins([]string{"*localhost*"}),
	)

	// Routing to API handlers
	router.Route("/api", func(router chi.Router) {
		router.With(CSRF).Get("/", Get)
		router.With(CSRF).Post("/", Post)
	})

	srv := &http.Server{
		Handler:      router,
		Addr:         "localhost" + ":" + "8000",
		ReadTimeout:  time.Duration(15) * time.Second,
		WriteTimeout: time.Duration(10) * time.Second,
		IdleTimeout:  time.Duration(5) * time.Second,
	}

	fmt.Printf("starting http server on port %s...\n", "8000")
	log.Fatal(srv.ListenAndServe())

}

func Get(w http.ResponseWriter, r *http.Request) {
	w.Header().Add("X-CSRF-Token", csrf.Token(r))
}

func Post(w http.ResponseWriter, r *http.Request) {}

Javascript:

axios.defaults.withCredentials = true

let get = async() => {
    let csrfToken = ""
    try {
        let resp = await axios.get("http://localhost:8000/api")
        console.log(resp)
        console.log(resp.headers)
        csrfToken = resp.headers["x-csrf-token"]
        console.log(csrfToken)
    } catch (err) {
        console.log(err)
    }

    const instance = axios.create({
        withCredentials: true,
        headers: {"X-CSRF-Token": csrfToken}
    })
    console.log(instance)
    return instance
}

let post = async(instance) => {
    try {
        let resp = await instance.post("http://localhost:8000/api", {})
        console.log(resp)
    } catch (err) {
        console.log(err)
    }
}

get()
    .then(axiosInstance => {
        post(axiosInstance)
    })

The error always comes from csrf.go, line 216:

realToken, err := cs.st.Get(r)

err is always "http: named cookie not present"

So it seems that axios is not sending it?

someone know it?
almost same problem

I actually figured this out, at least for serving on localhost: https://github.com/francoposa/go-csrf-examples (sorry for no documentation yet but the code is simple).

It's about the CORS settings, here's what I use for the API server (see config.local.yaml files in repo).
It's a shame CORS is not mentioned in the documentation.

---
server:
  host: localhost
  port: 8080
  timeout:
    server: 30
    read: 15
    write: 10
    idle: 5
  cors:
    allowCredentials: true
    allowedHeaders:
      - X-CSRF-Token
    exposedHeaders:
      - X-CSRF-Token
    allowedOrigins:
      - http://localhost*
    debug: true
  csrf:
    secure: false  # false in development only!
    key: place-your-32-byte-long-key-here
    cookieName: csrf
    header: X-CSRF-Token

For the UI side, I wrote a quick static file server so that the JavaScript is served from localhost. Just opening the index.html file in the browser will not register to the API server as the requests coming from localhost.

Also see that Axios lowercases all the headers it receives from the response: https://github.com/francoposa/go-csrf-examples/blob/main/ui/axios-js/web/static/index.js#L6

@francoposa are there specific changes to the docs you can suggest given the above?

Hi @DavidLarsKetch

I have forked with the intention of doing all of the below, but have been otherwise occupied since then.

If anyone feels inspired to tackle it before I get to it, I do feel pretty confident that the CORS configuration in my example repo is the absolute minimum config to get this working, with no extra stuff. I played around with this for days trying to get it as simple as possible.

Documentation updates in order of effort and helpfulness least to most:

  1. Some mention that the JavaScript examples won't work without applying CORS configuration to the server
  2. Linking to suggested CORS libraries
  3. Describing what a working CORS configuration would be
  4. Working code examples
    ... Extra credit? Maybe a basic description of the necessary CORS settings (AllowCredentials, ExposedHeaders, AllowedHeaders) and why they're needed

@francoposa thanks for the direction. I'll throw updated docs together if you want to hand that off.

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.

Not stale; still an issue and the PR to fix has not been looked at to my knowledge