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.
It's getting crazy out there* Let's cool down a bit?
Join 800+ icecold subscribers
*Political, social and economical trust keeps eroding. AI is adding non-deterministic fuel to the fire.
I'm currently building attested.systems, a way to make systems verifiable by humans and machines. Sharing what I learn along the way.
Hi! I'm the Willem behind willem.dev
I initially created this website to help developers learn Go, but I'm currently working a project that allows humans and machines to verify remote systems.
You can follow me on Bluesky or LinkedIn.
Happy that you're here and thanks for reading! :)