浏览器无法保存由Golang后端发送的cookie。

huangapple go评论78阅读模式
英文:

Browser not saving cookie sent by Golang backend

问题

我知道这个问题已经被问了很多次,但我尝试了大部分的答案,仍然无法使其工作。

我有一个使用net/http包的Golang API和一个JS前端。我有一个函数:

func SetCookie(w *http.ResponseWriter, email string) string {
    val := uuid.NewString()
    http.SetCookie(*w, &http.Cookie{
        Name:     "goCookie",
        Value:    val,
        Path:     "/",
    })
    return val
}

当用户登录时,会调用这个函数,我希望它能发送到所有其他的端点。这在Postman中按预期工作。然而,当涉及到浏览器时,我似乎无法让它记住cookie,甚至将其发送到其他端点。

JS使用端点的示例:

async function getDataWithQuery(query, schema){
    
    let raw = `{"query":"${query}", "schema":"${schema}"}`;
    let requestOptions = {
        method: 'POST',
        body: raw,
        redirect: 'follow',
    };
    try{
        let dataJson = await fetch("http://localhost:8080/query/", requestOptions)
        data = await dataJson.json();
    }catch(error){
        console.log(error);
    }

    return data;
}

我尝试了一些答案,比如在Golang中设置SameSite属性,或者在JS中使用credential: "include",但都没有成功。

英文:

I know this question has been asked a bunch of times, but I tried most of the answers and still can't get it to work.

I have a Golang API with net/http package and a JS frontend. I have a function

func SetCookie(w *http.ResponseWriter, email string) string {
	val := uuid.NewString()
	http.SetCookie(*w, &http.Cookie{
		Name:     "goCookie",
		Value:    val,
		Path:     "/",
	})
	return val
}

This function is called when the user logs in, and I expect it to be sent to all the other endpoints. This works as expected with Postman. However, when it comes to the browser, I can't seem to get it to remember the cookie or even send it to other endpoints.

An example of JS using an endpoint

async function getDataWithQuery(query, schema){
    
    let raw = `{"query":"${query}", "schema":"${schema}"}`;
    let requestOptions = {
        method: 'POST',
        body: raw,
        redirect: 'follow',
    };
    try{
        let dataJson = await fetch("http://localhost:8080/query/", requestOptions)
        data = await dataJson.json();
    }catch(error){
        console.log(error);
    }

    return data;
}

I tried answers like setting SameSite attribute in Golang, or using credential: "include" in JS with no luck.

答案1

得分: 1

感谢评论中的讨论,我找到了一些关于这个问题的提示。

保存 cookie(API 和前端在同一主机上)

我使用 document.cookie 来保存 cookie。我手动设置选项,因为在调用 API 的 fetch 响应上调用 res.cookie 只会返回值。一个示例是 document.cookie = `goCookie=${res.cookie}; path=/; domain=localhost;.

这个问题之前在之前的问题中已经回答过,并且在评论中再次回答了。问题是我使用了 credential:'include' 而不是正确的 credentials:'include'(复数形式)。

如果 API 和前端不在同一主机上,你需要修改 API 和前端。

前端

cookie 必须具有 API 的域,因为是 API 需要它,而不是前端。所以,出于安全原因,你不能为一个域(API)设置来自另一个域(前端)的 cookie。一个解决方案是将用户重定向到一个返回响应头中的 Set-Cookie 的 API 端点。这个解决方案会告诉浏览器将 cookie 注册到附加的域上(API 的域,因为 API 发送了它)。

此外,你仍然需要在前端中包含 credentials:'include'

API

你需要设置一些头部。我设置的头部如下:

	w.Header().Set("Access-Control-Allow-Origin", frontendOrigin)
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, withCredentials")
	w.Header().Set("Access-Control-Allow-Methods", method) // 使用端点的方法:POST、GET、OPTIONS

你需要公开前端将重定向用户并在响应中设置 cookie 的端点。你可以省略手动设置 API 的域,浏览器会自动填充它。

为了处理 CORS 并让 JS 成功发送 cookie,你需要在 cookie 中设置 SameSite=NoneSecure 属性,并通过 https 提供 API(我使用 ngrok 来简化)。

像这样:

func SetCookie(w *http.ResponseWriter, email string) string {
	val := uuid.NewString()
	http.SetCookie(*w, &http.Cookie{
		Name:     "goCookie",
		Value:    val,
		SameSite: http.SameSiteNoneMode,
		Secure:   true,
		Path:     "/",
	})
   // 其余的代码
}

我建议你也阅读一下使用 localStoragedocument.cookie 的区别,这是我遇到的一个问题。

希望对你有所帮助。

英文:

Thanks to the discussion in the comments, I found some hints about the problem.

Saving cookies (both API and frontend on the same host)

I used document.cookie to save the cookie. I set the options by hand since calling res.cookie on the response of the API fetch only returned the value. An example is document.cookie = `goCookie=${res.cookie}; path=/; domain=localhost;.

Sending cookies

This has been answered before in previous questions and answered again in the comments. The problem was that I used credential:'include' instead of the correct credentials:'include' (plural).

CORS and cookies

In case the API and the frontend are not on the same host you will have to modify both the API and the frontend.

frontend

The cookie has to have the domain of the API since it's the API that requires it, not the frontend. So, for security reasons, you can't set a cookie for a domain (API) from another domain (frontend). A solution would be redirect the user to an API endpoint that returns Set-Cookie header in the response header. This solution signals the browser to register that cookie with the domain attached to it (the API's domain, since the API sent it).

Also, you still need to include credentials:'include' in the frontend.

API

You will need to set a few headers. The ones I set are

	w.Header().Set("Access-Control-Allow-Origin", frontendOrigin)
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, withCredentials")
	w.Header().Set("Access-Control-Allow-Methods", method) // use the endpoint's method: POST, GET, OPTIONS

You need to expose the endpoint where the frontend will redirect the user and set the cookie in the response. Instead of setting the domain of the API by hand, you can omit it, the browser will fill it with the domain automatically.

To handle the CORS and let JS send the cookie successfully, you will have to set the SameSite=None and Secure attributes in the cookie and serve the API over https (I used ngrok to make it simple).

Like so

func SetCookie(w *http.ResponseWriter, email string) string {
	val := uuid.NewString()
	http.SetCookie(*w, &http.Cookie{
		Name:     "goCookie",
		Value:    val,
		SameSite: http.SameSiteNoneMode,
		Secure:   true,
		Path:     "/",
	})
   // rest of the code
}

I recommend you also read the difference between using localStorage and document.cookie, it was one of the problems I had.

Hope this helps.

huangapple
  • 本文由 发表于 2022年12月29日 21:46:37
  • 转载请务必保留本文链接:https://go.coder-hub.com/74951913.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定