Database Session Store with Elixir and Plug

Kim Lindholm
3 min readFeb 13, 2021

Writing your first encrypted DB-backed session store with sliding timeout

Photo by Vadim Morozov on Unsplash.

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.

The code is available on GitHub.

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.