Cannot set cookie in client from a Go API
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.
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?
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.