无法从 Go API 在客户端设置 cookie。

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

Cannot set cookie in client from a Go API

问题

我有一个用Go编写的后端,托管在Heroku上,我们称之为https://foo.herokuapp.com。我有一个托管在不同域名上的前端,我们称之为https://ui.example.com
后端API有一个/api/user/login的端点,它会以cookie的形式返回一个JSON Web Token,代码如下所示:

http.SetCookie(w, &http.Cookie{
    Name:     "token",
    Value:    token, // JWT
    HttpOnly: false, // 测试时设置为true,修复后再改为true
    MaxAge:   int(time.Hour * 24 * 3),
    Expires:  time.Now().UTC().Add(time.Hour * 24 * 3),
    Path:     "/",
    Secure:   true,
    SameSite: http.SameSiteNoneMode,
})

这是我在服务器上的CORS设置:

crossOrigin := cors.New(cors.Options{
    AllowedOrigins:   []string{allowedOrigin},
    AllowCredentials: true,
    AllowedMethods:   []string{http.MethodGet, http.MethodPost, http.MethodPut},
})

前端向后端发起请求的代码如下:

const endpoint = "/api/user/login/"
fetch(host + endpoint, {
    method: "POST",
    credentials: 'include',
    body: JSON.stringify({
        email,
        password
    })
}).then((response) => console.log(response))
.catch((err) => console.log(err));

问题:
现在,在浏览器的网络选项卡中可以看到这个cookie,但在应用程序选项卡(或Firefox中的存储选项卡中的cookie)中找不到这个cookie。浏览器没有保存这个cookie,这导致后续的请求失败,因为在处理实际请求之前,会验证和解码cookie中的令牌。

在另一个相关的主题中,我了解到Heroku在到达我的应用程序之前终止了SSL。因此,无法为非SSL流量设置安全cookie。那个解决方案建议信任X-Forwarded-For中找到的方案。我使用https://github.com/gorilla/handlers包启用了这个功能,代码如下所示:

// srv是我的实际处理程序
// crossOrigin是CORS中间件
// 最后由ProxyHeaders中间件包装
handlers.ProxyHeaders(crossOrigin.Handler(srv))

然而,这并没有起作用。

我阅读了许多主题/博客,到目前为止都没有起作用。我做错了什么?

英文:

I have a backend written in Go, hosted on Heroku, let's call it, https://foo.herokuapp.com. I have a frontend hosted on a different domain, let's call it, https://ui.example.com.
The backend API has an endpoint /api/user/login which sends back a JSON Web Token in the form of cookie shown as below:

http.SetCookie(w, &http.Cookie{
		Name:     "token",
		Value:    token, // the JWT
		HttpOnly: false, // for testing, set to true later once I fix this
		MaxAge:   int(time.Hour * 24 * 3),
		Expires:  time.Now().UTC().Add(time.Hour * 24 * 3),
		Path:     "/",
		Secure:   true,
		SameSite: http.SameSiteNoneMode,
})

These are my CORS settings on the server.

crossOrigin := cors.New(cors.Options{
		AllowedOrigins:   []string{allowedOrigin},
		AllowCredentials: true,
		AllowedMethods:   []string{http.MethodGet, http.MethodPost, http.MethodPut},
})

The frontend makes a request to the backend as given below.

const endpoint = "/api/user/login/"
fetch(host + endpoint, {
    method: "POST",
    credentials: 'include',
    body: JSON.stringify({
        email,
        password
    })
}).then((response) => console.log(response))
.catch((err) => console.log(err));

PROBLEM:
Now this cookie is actually visible in my browser's Network tab.
无法从 Go API 在客户端设置 cookie。

But the cookie does not exist in the application tab (or Storage tab in firefox where cookies exist). The browser is not saving the cookie, which is causing the subsequent requests to fail as the token in the cookie is verified and decoded before processing the actual request.

In another somewhat related thread, I got to know that Heroku terminates SSL before reaching my app. And, thus secure cookies cannot be set for non-SSL traffic. The solution there suggests trusting the scheme found in X-Forwarded-For. I enabled that using using the https://github.com/gorilla/handlers package as follows.

// srv is my actual handler
// crossOrigin is the CORS middleware
// finally wrapped by ProxyHeaders middleware
handlers.ProxyHeaders(crossOrigin.Handler(srv))

Yet this is not working.

I read many threads/blogs. Nothing has worked so far. What am I doing wrong?

答案1

得分: 3

Cookie是由浏览器保存的,保存在设置该Cookie的域名下。仅仅因为你在应用程序选项卡中看不到Cookie,并不意味着Cookie没有被保存。

如果你的前端https://ui.example.com发起了一个XHR调用到https://foo.herokuapp.com,并且该调用返回了一个Set-Cookie头部,那么浏览器会将该Cookie保存在foo.herokuapp.com域名下。你在ui.example.com的应用程序选项卡中看不到它。然而,当你再次向foo.herokuapp.com发起XHR调用时,浏览器会发送之前设置的Cookie。

你可以进行如下实验:登录后,打开一个新的标签页并导航到https://foo.herokuapp.com。现在打开应用程序选项卡,你应该在那里看到你的Cookie。

话虽如此,要记住浏览器会将这些Cookie视为第三方Cookie,浏览器供应商最终会停止支持第三方Cookie。最终,你应该确保你的前端和后端是从同一个父域名提供的。

至于另一个问题 - Heroku在其网关和你的应用程序之间终止SSL并不是一个问题。Cookie上的secure标志是给浏览器的信息 - 浏览器不会在非SSL连接上接受或发送带有此标志的Cookie。你的浏览器与Heroku服务器之间的连接是SSL的,所以Cookie会被接受/发送。在后端,Cookie只是HTTP头部,后端对Cookie的标志和连接类型并不关心。

英文:

Cookies are saved by the browser for the domain which set the cookie in the first place. Only becasue you can't see the cookie in the Application tab, does not mean that the cookie wasn't saved.

If your frontend https://ui.example.com makes an XHR call to https://foo.herokuapp.com and that call returns a Set-Cookie header, then the browser saves that cookie under foo.herokuapp.com domain. You will not see it in the ui.example.com's Application tab. Still, when you make another XHR call to foo.herokuapp.com then the browser will send the cookies that you've set earlier.

You can make this experiment: After logging in, open a new tab and navigate to https://foo.herokuapp.com. Now open the Application tab and you should see your cookies there.

That said, remember that the browser will treat these cookies as 3rd party cookies, and browser vendors will eventually drop support for 3rd party cookies. Eventually you should make sure that your frontend and backend are served from the same parent domain.

As for the other problem - Heroku's termination of SSL between their gateway and your app is not a problem. The secure flag on a cookie is an information for the browser - the browser will not accept or send a cookie with this flag over a non-SSL connection. The connection between your browser and the heroku server is SSL, so cookies will be accepted/sent. In your backend, cookies are just HTTP headers, and the backend does not really care neither about the cookies' flags nor by the connection type.

huangapple
  • 本文由 发表于 2022年1月11日 15:35:17
  • 转载请务必保留本文链接:https://go.coder-hub.com/70663097.html
匿名

发表评论

匿名网友

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

确定