Yesterday, I was working on the end-to-end tests for my example web application, (specifically this PR). But I ran into an issue.
- In the web browser I was able to successfully login and access a protected page.
- Using Go code and a
http.ClientI got a successful response but the session cookie was not set.
The session cookie
After a successful login, my app sets a session cookie. This cookie has two important attributes:
HttpOnly: Meaning the cookie is inaccessible via Javascript.Secure: Meaning the cookie should only be sent over an encrypted request via HTTPS.
You can find more information about these attributes on MDN.
Dealing with localhost
Most (all?) webbrowsers ignore the HTTPS requirement when the cookie is sent to a localhost hostname. This is to make local development easier I assume.
However, the Go net/http/cookiejar package doesn’t make this exception, it requires HTTPS even for localhost hostnames. Secure cookies are (silently) not included in such requests.
There is an open proposal to change this behavior,
Demo
package main
import (
"fmt"
"log"
"net/http"
"net/http/cookiejar"
"time"
"golang.org/x/net/publicsuffix"
)
func main() {
s := &http.Server{
Addr: ":9999",
Handler: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Printf("cookies in request: %v\n", r.Cookies())
// return a secure cookie
http.SetCookie(w, &http.Cookie{
Name: "secureCookie",
Value: "hello world!",
Secure: true,
})
}),
}
go func() {
err := s.ListenAndServe()
if err != nil {
log.Fatalf("failed to listen and serve: %v", err)
}
}()
// Create a HTTP client with a cookiejar.
jar, err := cookiejar.New(&cookiejar.Options{
PublicSuffixList: publicsuffix.List,
})
if err != nil {
log.Fatalf("failed to create cookiejar: %v", err)
}
client := &http.Client{
Timeout: time.Second,
Jar: jar,
}
// Do two requests.
for i := 0; i < 2; i++ {
res, err := client.Get("http://localhost:9999")
if err != nil {
log.Fatalf("failed to do request: %v", err)
}
defer res.Body.Close()
fmt.Printf("cookies in response: %v\n", res.Cookies())
}
}
If you run the example you will see that the cookies are always returned in the response, but never included in the requests.
If you change the Secure attribute to false (or remove it), you will see that the cookies are included in the second request.
My solution
In my app I added an option to disable Secure cookies for my end-to-end tests.
Get my free newsletter periodically*
Used by 500+ developers to boost their Go skills.
*working on a big project as of 2025, will get back to posting on the regular schedule once time allows.