Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Closed
francoposa opened this issue Nov 17, 2021 · 8 comments · Fixed by #162
Closed

Comments

@francoposa
Copy link
Contributor

francoposa commented Nov 17, 2021

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)
    })
@francoposa francoposa changed the title Gorilla CSRF always sets the same cookie, even as csrf.Token(r) varies Cannot get basic version of Javascript application documentation working Nov 17, 2021
@francoposa francoposa changed the title Cannot get basic version of Javascript application documentation working Cannot get basic version of in-browser Javascript application documentation working Nov 17, 2021
@francoposa
Copy link
Contributor Author

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?

@masayomitan
Copy link

masayomitan commented Dec 22, 2021

someone know it?
almost same problem

@francoposa
Copy link
Contributor Author

francoposa commented Dec 22, 2021

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

@DavidLarsKetch
Copy link

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

@francoposa
Copy link
Contributor Author

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

@DavidLarsKetch
Copy link

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

@stale
Copy link

stale bot commented Apr 16, 2022

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.

@stale stale bot added the stale label Apr 16, 2022
@stale stale bot closed this as completed Apr 27, 2022
@francoposa
Copy link
Contributor Author

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

coreydaley added a commit that referenced this issue Aug 17, 2023
Fixes #158, which is essentially that
1. none of the examples in the README for working with a JavaScript
frontend will work without proper CORS config on the backend
2. there is no example at all for using the HTTP header instead of
getting the CSRF token from the hidden form field

**Summary of Changes**

I have merged/copied over these simplified examples from my own
repository of working examples.

I was not sure how the maintainers may want to reference these examples
in the main README. Copying them over to the README verbatim would be
putting a lot of code into the README, but without changing the current
README, the content there differs significantly from the examples.

---------

Co-authored-by: Corey Daley <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants