Iv'e been setting and retrieving cookies over pre-WebSocket stage to identify a user. I assumed everything would work as over a typical HTTP exchange.
This has worked flawlessly on all browsers I've tested them on, but reports started coming in that on iPhones the sign-ins would not be retained at all, signifying that cookies either were not set or sent back to the server.
// fret not, safety checks removed for brevity
const (
sessionKeyCookieName string = "session-key"
webSocketPath string = "/ws"
)
func serveWs(w http.ResponseWriter, r *http.Request) {
var sessionKey [sha1.Size]byte
var u *user
for _, cookie := range r.Cookies() {
if cookie.Name != sessionKeyCookieName {
continue
}
slice, err := base64.StdEncoding.DecodeString(cookie.Value)
if err != nil {
continue
} else {
copy(sessionKey[:], slice)
}
}
u, _ = getUserBySessionKey(sessionKey)
// regenerate key. TODO: does that add security?
rand.Read(sessionKey[:])
header := make(http.Header)
header.Add("Set-Cookie", (&http.Cookie{
Name: sessionKeyCookieName,
Value: base64.StdEncoding.EncodeToString(sessionKey[:]),
MaxAge: int(sessionLength.Seconds()),
HttpOnly: true,
Domain: strings.SplitN(r.Host, ":", 2)[0],
}).String())
ws, err := upgrader.Upgrade(w, r, header)
if err != nil {
if _, ok := err.(websocket.HandshakeError); !ok {
log.Println(err)
}
return
}
// do things to `user` so their messages go to where they're needed
go c.writePump()
c.readPump()
}
Headers as seen on Firefox network dev tool
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: eSazcZyZKj2dfa2UWSY+a4wThC8=
Access-Control-Allow-Origin: *
Set-Cookie: session-key=RNStK2z2gAsan7DyNKQ+efjyr7c=; Domain=redacted.org; Max-Age=259200; HttpOnly
Am I skipping some step that would allow Safari to store cookies, or is this an issue upstream1?
P.S. I'd really like to retain this approach, since I can use HTTP-only cookies and that mostly ensures that JavaScript has no access to them.
TLDR: it's the HttpOnly
flag.
It appears that while some browsers do allow Set-Cookie
header in a response for WebSocket connection to have HttpOnly
flag, iOS Safari considers the situation as "non-HTTP" and blocks this.
Interestingly, while setting HttpOnly
cookies is not possible, HttpOnly
cookies are sent in request headers while connecting a WebSocket. This leaves a pair of options:
HttpOnly
;I'd consider iOS Safari's behavior to be incorrect compared to what's outlined in RFC 6265 Storage model
If your Set-Cookie
header works in other browsers my guess is it's an upstream issue, specifically iOS Safari has the ability to block cookies. By default iOS Safari blocks 3rd party cookies.
Can a webpage in mobile Safari check whether Settings > Safari > Accept Cookies 'From visited' or 'Always' is selected?
If cookies are blocked you can't use them. If you need cookies, detect support by setting a cookie on the login page like enabled=1
and then check for it in /ws
handler. If it comes up blank and cookies are blocked you can try redirecting to /please-enable-cookies
to ask the user to enable cookies for your site.
Another option is to store signed session data in local storage and include it in each request in the Authorization
header. https://jwt.io/
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With