securecookie encodes and decodes authenticated and optionally encrypted cookie values.
Secure cookies can't be forged, because their values are encrypted and validated using ChaCha20-Poly1305, the content is inaccessible to malicious eyes. It is still recommended that sensitive data not be stored in cookies, and that HTTPS be used to prevent cookie replay attacks.
To use it, first create a new SecureCookie instance:
// Keys must be 32 bytes long
var key = []byte("32-byte-long-auth-key")
var s, err = securecookie.New(hashKey, blockKey)
The key is required and must be 32 bytes, used to authenticate and encrypt cookie values.
Strong keys can be created using the convenience function
GenerateRandomKey()
. Note that keys created using GenerateRandomKey()
are not
automatically persisted. New keys will be created when the application is
restarted, and previously issued cookies will not be able to be decoded.
Once a SecureCookie instance is set, use it to encode a cookie value:
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := s.Encode("cookie-name", value); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
Secure: true,
HttpOnly: true,
}
http.SetCookie(w, cookie)
}
}
Later, use the same SecureCookie instance to decode and validate a cookie value:
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
if err = s2.Decode("cookie-name", cookie.Value, &value); err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
Secure cookies can hold any value thatcan be encoded using encoding/json. it works out of the box.
Rotating keys is an important part of any security strategy. The EncodeMulti
and
DecodeMulti
functions allow for multiple keys to be rotated in and out.
For example, let's take a system that stores keys in a map:
// keys stored in a map will not be persisted between restarts
// a more persistent storage should be considered for production applications.
var previous, _ = securecookie.New(securecookie.GenerateRandomKey(32))
var current, _ = securecookie.New(securecookie.GenerateRandomKey(32))
var cookies = map[string]*securecookie.SecureCookie{
"previous": previous,
"current": current,
}
Using the current key to encode new cookies:
func SetCookieHandler(w http.ResponseWriter, r *http.Request) {
value := map[string]string{
"foo": "bar",
}
if encoded, err := securecookie.EncodeMulti("cookie-name", value, cookies["current"]); err == nil {
cookie := &http.Cookie{
Name: "cookie-name",
Value: encoded,
Path: "/",
}
http.SetCookie(w, cookie)
}
}
Later, decode cookies. Check against all valid keys:
func ReadCookieHandler(w http.ResponseWriter, r *http.Request) {
if cookie, err := r.Cookie("cookie-name"); err == nil {
value := make(map[string]string)
err = securecookie.DecodeMulti("cookie-name", cookie.Value, &value, cookies["current"], cookies["previous"])
if err == nil {
fmt.Fprintf(w, "The value of foo is %q", value["foo"])
}
}
}
Rotate the keys. This strategy allows previously issued cookies to be valid until the next rotation:
func Rotate(newCookie *securecookie.SecureCookie) {
cookies["previous"] = cookies["current"]
cookies["current"] = newCookie
}
BSD licensed. See the LICENSE file for details.