Database Session Store with Elixir and Plug
Writing your first encrypted DB-backed session store with sliding timeout
If you’ve ever caught yourself pushing large encrypted content into the standard Plug.Session.COOKIE
store, you may have noticed that hitting the 4096-byte cookie size limit isn't all that difficult. And if that hard limit becomes a deal breaker, next you'd probably be inclined to reach for Plug.Session.ETS
which in turn comes with the small gotcha that if your app runs in a distributed environment, now you'll have to deal with syncing ETS tables across nodes - something you may or may not be comfortable with.
In this post, we’ll explore another option, that is, rolling your own DB-backed session store. Using the tests of both of the said stores as a guideline, we’ll write ourselves an implementation of Plug.Session.Store
that supports session expiry after inactivity and after adding optional signing and encryption to it, we'll wrap up by briefly discussing an important caveat.
First Iteration
To start off with the simplest thing that could possibly work, here’s our initial version that uses Erlang’s term_to_binary/1
to write data into the sessions
table.
Commits on GitHub
Signing and Encryption
Instead of reinventing the wheel, let’s take a peek at the open-source implementation of Plug.Session.COOKIE
that uses Plug.Crypto
utils and apply it to our own store.
The commit below has full details but the brass tacks are we add optional calls to Plug.Crypto.MessageVerifier.sign/3
and Plug.Crypto.MessageEncryptor.encrypt/3
, respectively. For example:
Commits on GitHub
Next Steps
Something we’d obviously want to do before going live with our store is to whip up an Oban worker or an equivalent for periodically cleaning up expired sessions.
Another pending issue is that with the cookie store now replaced with a database-backed implementation, we have to be vigilant about session fixation attacks. What OWASP instructs us to do here is to Renew the Session ID After Any Privilege Level Change.
The session ID must be renewed or regenerated by the web application after any privilege level change within the associated user session. […]
For all these web application critical pages, previous session IDs have to be ignored, a new session ID must be assigned to every new request received for the critical resource, and the old or previous session ID must be destroyed.
Luckily for us, Plug.Conn.configure_session/2
helps us do just that. In a Phoenix app, you'd rotate the session like so:
after which dropping the session looks oddly familiar:
That’s it. Be sure to check out the repo and as always, comments and PRs are welcome.