After React I started getting into the backend and added github OAUTH and sessions on the backend server to save data. They all work fine on the backend and I can access the data from other handlers via sessions etc. But once I try to get the session from the backend using React, I can never do it.
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 a lot of things, first I used a different session library than gorilla, it's called scs, I thought it might be my library, but it's not the case. When changing the code I get different errors but the backend works fine every time. On API requests from the backend sometimes I get an empty string data, or a network error or user not found etc, but every time I check the backend in every iteration of the code it works fine. This is the get 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> </> }
P粉2370294572024-04-07 00:04:59
IIUC, the web page loads from http://localhost:5173
and makes a GET request to http://127.0.0.1:3080/user
. So this is a cross-origin request.
By default, the browser does not send credentials (such as Cookie and HTTP Authentication< /a>) in cross-origin XMLHttpRequest or Fetch calls. Certain flags must be set when calling the XMLHttpRequest object or the Request constructor.
To tell the browser to send cookies to cross-domain URLs, the fetch call 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 the backend code has the CORS headers configured correctly, so the above changes should make it work. If not, check your browser's DevTools console. It should contain some error/warning messages telling you what went wrong.
This is a minimal demo to help debug the issue.
Start the server: go run main.go
Navigate to http://127.0.0.1:3080/callback
in your browser to set the cookie.
Set-Cookie: session=abc;Path=/;Expires=Tuesday, April 18, 2023 18:34:49 GMT;Maximum age=86372;Http only;SameSite=Lax
.
Navigate to http://127.0.0.1:5173/
to open the page.
Click the Get button on this page. It should output the session cookie "abc" to the DevTools console.
Comments:
I just realized that the cookie is being saved to domain 127.0.0.1
(no port). So http://127.0.0.1:5173/
page can also read cookies.
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 = `
sssccc
`
Incognito windows cannot see the cookies of other windows. Please check your browser to make sure the cookie is present.