1

I started to get into backend after React and on my backend server I added github OAUTH and sessions to persist the data. They are all working fine in the backend side, I can accesses the data from other handlers with sessions etc. But as soon as I try to get the session from backend with react I am never able to.

func (h Handler) HandleAuth(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    w.Header().Set("Access-Control-Allow-Methods", "GET")
    url := Oauth2Config.AuthCodeURL("state", oauth2.AccessTypeOffline)
    http.Redirect(w, r, url, http.StatusFound)
}

func (h Handler) HandleAuthCallback(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    w.Header().Set("Access-Control-Allow-Methods", "GET")
    code := r.URL.Query().Get("code")
    token, err := Oauth2Config.Exchange(r.Context(), code)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }

    // Use the access token to get the user's GitHub data
    client := github2.NewTokenClient(r.Context(), token.AccessToken)
    user, _, err := client.Users.Get(r.Context(), "")
    if err != nil {
        fmt.Printf("Error: %v\n", err.Error())
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    session, err := store.Get(r, "session")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    session.Values["user"] = user.GetLogin()
    session.Values["access_token"] = token.AccessToken
    err = session.Save(r, w)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    fmt.Fprintf(w, "this is authcallback: %s", user.GetLogin())

}

func (h Handler) HandleCurrentUser(w http.ResponseWriter, r *http.Request) {
    w.Header().Set("Access-Control-Allow-Origin", "http://localhost:5173")
    w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
    w.Header().Set("Access-Control-Allow-Methods", "GET")
    session, err := store.Get(r, "session")
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    user, ok := session.Values["user"].(string)
    if !ok {
        http.Error(w, "Invalid user in session", http.StatusInternalServerError)
        return
    }
    // Set the content type header to JSON
    w.Header().Set("Content-Type", "text/plain")

    // Write the JSON data to the response
    w.Write([]byte(user))
}

I tried many things, first I was using a different sessions library other than gorilla which was called scs and I thought maybe it was my library but it was not. And while changing the code the error I got differed but every time the backend was working just fine. On the API request from the backend sometimes I would get an empty string for data, or network error or user not found etc., but every time I checked the backend in each iteration of code backend worked perfectly fine. Here is the fetch request:

function App() {
    const [user, setUser] = useState(null);

    useEffect(() => {
        fetch('http://127.0.0.1:3080/user', {
            method: 'GET',
        })
            .then(response => response.text())
            .then(data => {
                setUser(data);
                console.log(data);
            })
            .catch(error => console.error(error));
    }, []);
[]);
    return <>
    <p>Logged in as: {user}</p>
            <button onClick={() => window.location.href = 'http://127.0.0.1:3080/oauth'}>Login</button>

    </>
}

Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
ponwock
  • 11
  • 3

1 Answers1

1

IIUC, the web page is loaded from http://localhost:5173 and it makes a GET request to http://127.0.0.1:3080/user. So this is a cross-origin request.

By default, in cross-origin XMLHttpRequest or Fetch invocations, browsers will not send credentials (such as Cookies and HTTP authentication). A specific flag has to be set on the XMLHttpRequest object or the Request constructor when it is invoked.

In order to tell the browser to send cookies to a cross-origin URL, the fetch invocation should be changed like this:

  fetch('http://127.0.0.1:3080/user', {
    method: 'GET',
+   mode: 'cors',
+   credentials: 'include',
  })

For more information, see Requests with credentials.

It looks like that the backend code has configured the CORS headers correctly, so the above changes should make it work. If it doesn't, please check the DevTools console of the browser. It should contain some error/warning messages that will tell you what's wrong.


Here is the minimal demo helps to debug the issue.

  1. start the server: go run main.go

  2. navigate to http://127.0.0.1:3080/callback in a browser to set the cookie.

    Set-Cookie: session=abc; Path=/; Expires=Tue, 18 Apr 2023 18:34:49 GMT; Max-Age=86372; HttpOnly; SameSite=Lax.

  3. navigate to http://127.0.0.1:5173/ to open a page.

  4. click the fetch button on this page. It should output the session cookie "abc" to the DevTools console.

Notes:

I just realized that the cookie is saved to the domain 127.0.0.1 (without the port). So the page http://127.0.0.1:5173/ can read the cookie too.

package main

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

func main() {
    go func() {
        _ = http.ListenAndServe(":5173", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            w.Write([]byte(page))
        }))
    }()

    http.HandleFunc("/callback", func(w http.ResponseWriter, r *http.Request) {
        http.SetCookie(w, &http.Cookie{
            Name:     "session",
            Value:    "abc",
            Path:     "/",
            Expires:  time.Now().Add(24 * time.Hour),
            MaxAge:   86372,
            HttpOnly: true,
            SameSite: http.SameSiteLaxMode,
        })
        w.Write([]byte("done"))
    })

    http.HandleFunc("/user", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "http://127.0.0.1:5173")
        w.Header().Set("Access-Control-Allow-Headers", "Content-Type")
        w.Header().Set("Access-Control-Allow-Methods", "GET")
        w.Header().Set("Access-Control-Allow-Credentials", "true")

        cookie, err := r.Cookie("session")
        if err != nil {
            fmt.Fprintln(w, err.Error())
            return
        }
        w.Write([]byte(cookie.Value))
    })

    http.ListenAndServe(":3080", nil)
}

const page = `<html>
  <body>
    <button>fetch</button>
    <script>
      document.querySelector('button').addEventListener('click', () => {
        fetch('http://127.0.0.1:3080/user', {
          method: 'GET',
          credentials: 'include',
        })
          .then((response) => response.text())
          .then((data) => {
            console.log(data);
          })
          .catch((error) => console.error(error));
      });
    </script>
  </body>
</html>`

Incognito window can not see cookies from other window. Please check in the browser to make sure the cookie is there.

incognito window can not see cookies from other window

Zeke Lu
  • 6,349
  • 1
  • 17
  • 23
  • While I tried with credentials before it was still to no avail, i never tried with the mode prop but even after trying it like this i still get empty data on the frontend. After some research i wonder if i have to do anything more to decrypt the session in the backend ? i use a session library for it and save it to a variable and send that variable to the frontend but i don't know if it is still encrypted or not – ponwock Apr 17 '23 at 15:52
  • A session library should do the encryption (for saving to cookie) and decryption (after reading from cookie) for you. What have you seen in the DevTools console? Can you copy the requests as HAR and share them? I want to check what is sent on the wire first. – Zeke Lu Apr 17 '23 at 16:02
  • i assume you are asking the frontend requests, here they are https://wetransfer.com/downloads/1cf4a4dfe50443b7138ff2d47c4f956320230417163627/4576c4 – ponwock Apr 17 '23 at 16:39
  • Yes, that's what I want. It shows that the request to `http://127.0.0.1:3080/user` does not have the `Cookie` header. Since the session cookie is set by the response from HandleAuthCallback, can you export another HAR including the request to HandleAuthCallback? BTW, have you seen any error or warning messages in DevTools console? – Zeke Lu Apr 17 '23 at 17:11
  • No like i said, in the frontend i console log everything, the only thing i get is empty data. In the backend console, i log the cookie and i am able to see it in the console when i send a request `http://127.0.0.1:3080/user` through my browser. but when i send the same request through react the empty data gets logged there too https://wetransfer.com/downloads/f703bffbae2afa69ff48ab1f9a4dfb5220230417172947/9727eb – ponwock Apr 17 '23 at 17:30
  • I just updated the answer to include a minimal demo to make it easy to debug the issue. And I just realized that this is not a cross origin request issue (not very sure yet). Is it possible that you open the page `http://127.0.0.1:5173` in a totally different browser or in an incognito window? In that case, it can not access the cookie at all. – Zeke Lu Apr 17 '23 at 18:51
  • The fetch request to `http://127.0.0.1:3080/user` is blocked by CORS policy if I remove `w.Header().Set("Access-Control-Allow-Credentials", "true")`. So this is a cross origin request. Another thing to check is have you recompiled the frondend app or refresh the page to make sure `credentials: 'include'` takes effect? – Zeke Lu Apr 17 '23 at 19:18
  • I tried your solution by setting the cookie with http.cookie and this is the result. Somehow i just can't get the data. (ps: the webstorm session is my IDE) https://prnt.sc/2WQGtlGlVvIl https://prnt.sc/7LYFxlMZQkBs https://prnt.sc/QXi-v9xUYqN6 – ponwock Apr 17 '23 at 20:41
  • Have you tried my demo? (let's debug from a minimal demo, no react.js, no session library). – Zeke Lu Apr 17 '23 at 20:49
  • yep, this is working currently. I am able to get the `abc` logged in to the console when i click fetch. https://prnt.sc/ELYYD5wagQhr – ponwock Apr 17 '23 at 20:59
  • Then there must be something wrong in your code. Maybe a typo in `credentials: 'include'`? Maybe the js file is not updated? I'm not sure. Please pay some time to create a minimal reproducer. – Zeke Lu Apr 17 '23 at 21:02
  • i was currently focused on building the backend and just wanted to try to get the session data through frontend and i only have a useeffect code which i double checked and thats it. Thank you for your time, i will try to debug this issue,i still believe it is related to the session libraries and decyrpt vs encryption etc. – ponwock Apr 17 '23 at 21:06
  • A quick check. Is the `fetch` a standard API? Or you import it from a library? If it's imported from a library, maybe it's not `credentials: 'include'`. – Zeke Lu Apr 17 '23 at 21:07
  • "i still believe it is related to the session libraries and decyrpt vs encryption etc". no, according to the HAR, the fetch request does not send the cookie to the server at all. It has nothing to do with decryption or encryption. You can check the request in DevTools to confirm that. – Zeke Lu Apr 17 '23 at 21:09
  • "and just wanted to try to get the session data through frontend", then use a simple html page as the one in my demo. Try with the simple html in my demo with your backend to see whether it works. – Zeke Lu Apr 17 '23 at 21:11
  • Yes fetch is a standard api in js, and `credentials: "include"` is in the mdn. I see, then i should use the html server code you gave me with my sessions to see if it works then work my way up i suppose. Thanks – ponwock Apr 17 '23 at 21:17
  • You're welcome! Please provide a minimal reproducer if you need further help. – Zeke Lu Apr 17 '23 at 21:18
  • BTW, it seems that [node-fetch](https://www.npmjs.com/package/node-fetch) does not support the `credentials` option. Make sure you're not using it (You have mentioned that you are using the one provided by the browser. Just to double confirm). – Zeke Lu Apr 17 '23 at 21:20
  • i am using this one https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch – ponwock Apr 17 '23 at 21:24