gorilla/securecookie

repository·main·Indexed 20 days ago

https://github.com/gorilla/securecookie

A Go package providing tools to encode and decode authenticated and optionally encrypted cookie values. It uses HMAC to prevent forgery and AES encryption for privacy. The library supports custom serializers (Gob, JSON, Nop), configurable limits for MaxLength and MaxAge, and provides EncodeMulti and DecodeMulti functions to implement key rotation without invalidating existing cookies.

Tokens
2.7K
Snippets
9
Records
10
Agent score
23%

What's inside gorilla/securecookie

  1. Implement key rotation with EncodeMulti and DecodeMulti

    main

    To rotate keys without invalidating existing cookies, use securecookie.EncodeMulti and securecookie.DecodeMulti. This allows you to maintain a set of valid keys (e.g., a 'current' key and a 'previous' key).

    1. Encoding: Use securecookie.EncodeMulti(name, value, currentInstance) to ensure new cookies are always signed/encrypted with the latest key.
    2. Decoding: Use securecookie.DecodeMulti(name, encodedValue, &target, currentInstance, previousInstance...) to check the incoming cookie against all currently valid keys.
    3. Rotation Strategy: When rotating, move the current instance to previous and set a new instance as current.
    // 1. Setup multiple instances for rotation
    var cookies = map[string]*securecookie.SecureCookie{
    	"previous": securecookie.New(
    		securecookie.GenerateRandomKey(64),
    		securecookie.GenerateRandomKey(32),
    	),
    	"current": securecookie.New(
    		securecookie.GenerateRandomKey(64),
    		securecookie.GenerateRandomKey(32),
    	),
    }
    
    // 2. Encode using the current key
    // securecookie.EncodeMulti("cookie-name", value, cookies["current"])
    
    // 3. Decode against all valid keys
    // err = securecookie.DecodeMulti("cookie-name", cookie.Value, &value, cookies["current"], cookies["previous"])
    
    // 4. Rotate
    func Rotate(newCookie *securecookie.SecureCookie) {
    	cookies["previous"] = cookies["current"]
    	cookies["current"] = newCookie
    }
  2. Initialize a SecureCookie instance

    main

    To use securecookie, create a new instance using securecookie.New(hashKey, blockKey).

    • hashKey: Required. Used to authenticate the cookie value using HMAC. It is recommended to use a key with 32 or 64 bytes.
    • blockKey: Optional. Used to encrypt the cookie value. Set to nil to disable encryption. If provided, the length must correspond to the block size of the encryption algorithm (AES). For AES, valid lengths are 16, 24, or 32 bytes (for AES-128, AES-192, or AES-256 respectively).

    You can use securecookie.GenerateRandomKey() to create strong keys, but note that these are not automatically persisted; restarting the application with new random keys will invalidate previously issued cookies.

    // Hash keys should be at least 32 bytes long
    var hashKey = []byte("very-secret")
    // Block keys should be 16 bytes (AES-128) or 32 bytes (AES-256) long.
    var blockKey = []byte("a-lot-secret")
    var s = securecookie.New(hashKey, blockKey)
  3. Use different Serializers (Gob, JSON, Nop)

    main

    The Serializer interface allows you to choose how data is converted to bytes before encryption/signing.

    • GobEncoder: The default. Uses encoding/gob. Handles complex types if they are registered via gob.Register().
    • JSONEncoder: Uses encoding/json. Types must satisfy json.Marshaller and json.Unmarshaller.
    • NopEncoder: Does not encode/decode. It expects the input/output to be a []byte. Useful if you handle serialization upstream.
    // Using JSON instead of Gob
    sc.SetSerializer(securecookie.JSONEncoder{})
  4. Encode and Decode cookie values

    main

    Once a SecureCookie instance is initialized, you can encode and decode values.

    Encoding

    Use s.Encode(name, value) to transform a value into an encoded string. The value must be compatible with encoding/gob. For custom types, you must register them using gob.Register() first. Basic types work out of the box.

    Decoding

    Use s.Decode(name, encodedValue, &target) to validate and decode an encoded string into a target variable. The target must be a pointer to the type used during encoding.

    Note: An optional JSON encoder using encoding/json is available for types compatible with JSON.

    // Encoding example
    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)
    	}
    }
    
    // Decoding example
    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"])
    		}
    	}
    }
  5. Generate a random key

    main

    Use GenerateRandomKey(length int) to create a cryptographically secure random key of the specified byte length.

    Warning: If this returns nil, the system's random number generator failed. You should treat this as a critical failure and not proceed.

    key := securecookie.GenerateRandomKey(32)
    if key == nil {
        // handle failure
    }
  6. Handle errors with the Error interface

    main

    All errors returned by the library implement the Error interface, allowing you to categorize them:

    • IsUsage() bool: Indicates client code error (e.g., wrong key length, misconfigured serializer).
    • IsDecode() bool: Indicates a decoding/validation failure (e.g., expired, invalid MAC, decryption failed). These are expected with untrusted input.
    • IsInternal() bool: Indicates an unexpected implementation error.
    • Cause() error: Returns the underlying error if one was propagated.
  7. Decode a secure cookie string into a value

    main

    The Decode(name, value string, dst interface{}) method decodes, verifies the HMAC, optionally decrypts, and deserializes a cookie value.

    • name: The name used during encoding.
    • value: The encoded cookie string.
    • dst: A pointer to the destination variable where the decoded value will be stored.

    Since cookies are untrusted user input, you should expect errors of type IsDecode() == true. The proper action is usually to reject the request.

    var myData MyDataType
    err := sc.Decode("session", cookieValue, &myData)
    if err != nil {
        // handle error (e.g., reject request if err.IsDecode())
    }
  8. Implement Key Rotation with Multi-Codec functions

    main

    To support key rotation (allowing old cookies to be decoded while issuing new ones with new keys), use EncodeMulti and DecodeMulti with a slice of Codec objects.

    • EncodeMulti(name, value, codecs...): Tries codecs in order until one succeeds.
    • DecodeMulti(name, value, dst, codecs...): Tries codecs in order until one succeeds.
    • CodecsFromPairs(keyPairs...): A helper to create a slice of Codec instances from alternating hashKey and blockKey pairs. The last pair can be just a hashKey (no encryption).
    // Create codecs for rotation
    codecs := securecookie.CodecsFromPairs(
        []byte("new-hash-key"),
        []byte("new-block-key"),
        []byte("old-hash-key"),
        []byte("old-block-key"),
    )
    
    // Encode using the newest codec
    encoded, err := securecookie.EncodeMulti("session", data, codecs...)
    
    // Decode using all codecs (will try old ones if new ones fail)
    err := securecookie.DecodeMulti("session", encoded, &dst, codecs...)
  9. Encode a value into a secure cookie string

    main

    The Encode(name string, value interface{}) method serializes, optionally encrypts, signs with an HMAC, and base64-encodes a value.

    • name: The cookie name (stored within the encoded value).
    • value: The data to encode. The type must be compatible with the configured Serializer (default is GobEncoder).

    Returns the encoded string or an error. If the resulting string exceeds MaxLength, an error is returned.

    encoded, err := sc.Encode("session", myData)
    if err != nil {
        // handle error
    }
    // use 'encoded' as the cookie value
  10. Configure SecureCookie limits and behavior

    main

    You can chain configuration methods on a SecureCookie instance returned by New():

    • MaxLength(value int): Sets the maximum length in bytes for the cookie value (default 4096).
    • MaxAge(value int): Sets the maximum age in seconds (default 86400 * 30). Set to 0 for no restriction.
    • MinAge(value int): Sets the minimum age in seconds (default 0).
    • HashFunc(f func() hash.Hash): Sets the hash function for HMAC (default sha256.New).
    • BlockFunc(f func([]byte) (cipher.Block, error)): Sets the encryption function (default aes.New).
    • SetSerializer(sz Serializer): Sets the serialization method.
    sc := securecookie.New(hashKey, blockKey). 
        MaxLength(2048). 
        MaxAge(3600). 
        SetSerializer(securecookie.JSONEncoder{})