While I was implementing OAuth 2.0 authorization, I learned about Sinatra’s signed cookie sessions. Sinatra session data is stored in an HMAC-SHA256 signed user cookie and is only accessible if the signature is verified. Data is set and fetched on the session hash. The secret is set via set :session_secret, or is randomly generated at startup if not provided.

Sinatra sessions use the v2 Rack::Session middleware. Rack::Session v3 uses AES 256 CTR encryption.

Signed cookies are the same idea as JWTs (which also commonly use HMAC signatures), in that you can trust signed state stored with the user. This can be useful for lightweight stateless sessions on OAuth login apps, although most apps should use regular database-backed session cookies.

This security measure is also in place because Rack::Session treats session cookie data as trusted input. This article describes an interesting remote code execution (RCE) attack against a Rack app with a weak session secret, exploiting the fact that Rack::Session can deserialize Ruby objects.

Signed (or encrypted) cookies are a standard feature in server-side web frameworks. They were included in every framework I checked: Rails, Express.js, Django, and Phoenix/Plug.

Learning about signed/encrypted cookies, I found it odd how synonymous JWTs have become widespread instead of being regarded as an implementation detail of the web framework. A signed cookie can be implemented with a JWT in a cookie, but using them directly can be prone to mistakes. My hunch is that JWTs became popular because frontend frameworks like React leave these to the application developer.


For this topic, I Googled, read blog posts and framework docs, and looked over Rack::Session and Sinatra source code.