Rails 6 adds Purpose Metadata to Cookies


Action Dispatch provides cookies.signed and cookies.encrypted, which can be used to prevent users from tampering with a cookie’s value.

Example:

# Prevents users from tampering with this cookie’s value
cookies.signed[:proxy_id] = current_admin.id

# Resultant client side cookie's content
# MTAx--fd47225e9e6de0710a4f84104d73fec1e4d94c65

# Prevents users from reading and tampering with its value
cookies.encrypted[:current_zip] = current_user.zip

# Resultant client side cookie's content
# aHNzb2dxVkN1bE1MQTd0MnFsSkZ2dz09LS1tM2NHcVZQbjRoT0RVOVdvdE9FdHZnPT0%3D--be007d00dda3678f79fda9d2a7bcfacc6a760919

The problem

Before Rails 6, the methods described above only signed the content of the cookie and not the name.

This created a security vulnerability where signed data could potentially be copied from one cookie to another.

This issue presented a case where it is possible to copy the value of a cookie and use it for another.

After Rails 6

Rails 6 adds purpose metadata to cookies.

A cookie’s name is used to set this metadata, which is then embedded in the cookie.

This prevents users from using one cookie’s value for another.

Example:

cookies.signed[:proxy_id] = current_admin.id

# Resultant client side cookie's content
# eyJfcmFpbHMiOnsibWVzc2FnZSI6Ik1UQXgiLCJleHAiOm51bGwsInB1ciI6ImNvb2tpZS5wcm94eV9pZCJ9fQ%3D%3D--ac22ece5b73ea1fbd3de8e925be57a173e9f8a2b

In the example above, the data before -- is the Base64 encoded value with purpose and expiry metadata embedded.

Cookies previously set without this metadata will continue to be honored.

We can opt out of this feature with the following config:

config.action_dispatch.use_cookies_with_metadata = false

The above config is true by default.