How Rails Sessions Work

What if your Rails app couldn’t tell who was visiting it? If you had no idea that the same person requested two different pages? If all the data you stored vanished as soon as you returned a response?

That might be fine for a mostly static site. But most apps need to be able to store some data about a user. Maybe it’s a user id, or a preferred language, or whether they always want to see the desktop version of your site on their iPad.

session is the perfect place to put this kind of data. Little bits of data you want to keep around for more than one request.

Sessions are easy to use:

session[:current_user_id] = @user.id

But they can be a little magical. What is a session? How does Rails know to show the right data to the right person? And how do you decide where you keep your session data?

What is a session?

A session is just a place to store data during one request that you can read during later requests.

You can set some data in a controller action:

app/controllers/sessions_controller.rb
def create
  # ...
  session[:current_user_id] = @user.id
  # ...
end

And read it in another:

app/controllers/users_controller.rb
def index
  current_user = User.find_by_id(session[:current_user_id])
  # ...
end

It might not seem that interesting. But it takes coordination between your user’s browser and your Rails app to make everything connect up. And it all starts with cookies.

When you request a webpage, the server can set a cookie when it responds back:

~ jweiss$ curl -I http://www.google.com | grep Set-Cookie

Set-Cookie: NID=67=J2xeyegolV0SSneukSOANOCoeuDQs7G1FDAK2j-nVyaoejz-4K6aouUQtyp5B_rK3Z7G-EwTIzDm7XQ3_ZUVNnFmlGfIHMAnZQNd4kM89VLzCsM0fZnr_N8-idASAfBEdS; expires=Wed, 16-Sep-2015 05:44:42 GMT; path=/; domain=.google.com; HttpOnly

Your browser will store those cookies. And until the cookie expires, every time you make a request, your browser will send the cookies back to the server:

...
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: www.google.com
> Accept: */*
> Cookie: NID=67=J2xeyegolV0SSneukSOANOCoeuDQs7G1FDAK2j-nVyaoejz-4K6aouUQtyp5B_rK3Z7G-EwTIzDm7XQ3_ZUVNnFmlGfIHMAnZQNd4kM89VLzCsM0fZnr_N8-idASAfBEdS; expires=Wed, 16-Sep-2015 05:44:42 GMT; path=/; domain=.google.com; HttpOnly
...

Many cookies just look like gibberish. And they’re supposed to. Because the information inside the cookie isn’t meant for the user. Your Rails app is in charge of figuring out what a cookie means. Your app set it, so your app can read it.

What does this have to do with a session?

So, you have a cookie. You put data in during one request, and you get that same data in the next. What’s the difference between that and a session?

By default, in Rails, there isn’t much of a difference. Rails does some work with the cookie to make it more secure. But besides that, it works the way you’d expect. Your Rails app puts some data into the cookie, the same data comes out of the cookie. If this was all there was, there’d be no reason to distinguish sessions from cookies.

But cookies aren’t always the right answer for session data:

  • You can only store about 4kb of data in a cookie.

    This is usually enough, but sometimes it’s not.

  • Cookies are sent along with every request you make.

    Big cookies mean bigger requests and responses, which mean slower websites.

  • If you accidentally expose your secret_key_base, your users can change the data you’ve put inside your cookie.

    When this includes things like current_user_id, anyone can become whichever user they want!

  • Storing the wrong kind of data inside a cookie can be insecure.

If you’re careful, these aren’t big problems.

But when you can’t store your session data inside a cookie for one of these reasons, Rails has a few other places to keep your sessions:

Alternative session stores

All of the session stores that aren’t the cookie session store work in pretty much the same way. But it’s easiest to think about using a real example.

If you were keeping track of your sessions with ActiveRecord:

  1. When you call session[:current_user_id] = 1 in your app, and a session doesn’t already exist:

  2. Rails will create a new record in your sessions table with a random session ID (say, 09497d46978bf6f32265fefb5cc52264).

  3. It’ll store {current_user_id: 1} (Base64-encoded) in the data attribute of that record.

  4. And it’ll return the generated session ID, 09497d46978bf6f32265fefb5cc52264, to the browser using Set-Cookie.

The next time you request a page,

  1. The browser sends that same cookie to your app, using the Cookie: header.

    (like this: Cookie: _my_app_session=09497d46978bf6f32265fefb5cc52264;
    path=/; HttpOnly)

  2. When you call session[:current_user_id]:

  3. Your app grabs the session ID out of your cookie, and finds its record in the sessions table.

  4. Then, it returns current_user_id out of the data attribute of that record.

Whether you’re storing sessions in the database, in Memcached, in Redis, or wherever else, they mostly follow this same process. Your cookie only contains a session ID, and your Rails app looks up the data in your session store using that ID.

When it works, storing your sessions in cookies is by far the easiest way to go. It doesn’t need any extra infrastructure or setup.

But if you need to move beyond the cookie session store, you have two options:

Store sessions in a database, or store them in your cache.

Storing sessions in the cache

You might already be using something like Memcache to cache your partials or data. If so, the cache store is the second-easiest place to store session data, since it’s already set up.

You don’t have to worry about your session store growing out of control, because older sessions will automatically get kicked out of the cache if it gets too big. And it’s fast, because your cache will most likely be kept in memory.

But it’s not perfect:

  • If you actually care about keeping old sessions around, you probably don’t want them to get kicked out of the cache.

  • Your sessions and your cached data will be fighting for space. If you don’t have enough memory, you could be facing a ton of cache misses and early expired sessions.

  • If you ever need to reset your cache (say you upgraded Rails and your old cached data is no longer accurate), there’s no way to do that without expiring everyone’s sessions.

Still, this is how we store session data at Avvo, and it’s worked well for us so far.

Storing sessions in the database

If you want to keep your session data around until it legitimately expires, you probably want to keep it in some kind of database. Whether that’s Redis, ActiveRecord, or something else.

But database session storage also has drawbacks:

  • With some database stores, your sessions won’t get cleaned up automatically.

    So you’ll have to go through and clean expired sessions on your own.

  • You have to know how your database will behave when it’s full of session data.

    Are you using Redis as your session store? Will it try to keep all your session data in memory? Does your server have enough memory for that, or will it start swapping so badly you won’t be able to ssh in to fix it?

  • You have to be more careful about when you create session data, or you’ll fill your database with useless sessions.

    For example, if you accidentally touch the session on every request, googlebot could create hundreds of thousands of useless sessions. And that would be a bad time.

Most of these problems are pretty rare. But you should still be aware of them.

So how should you store your sessions?

If you’re pretty sure you won’t run into any of the cookie store’s limitations, use it. It doesn’t need much setup, and isn’t a headache to maintain.

Storing sessions in the cache vs. the database is more a judgement call about how bad it would be to expire a session early. I treat session data as pretty temporary, so the cache store works well for me. So I usually try cookie first, then cache, then database.

But how about you? How do you store your sessions? Leave a comment and let me know!

And if you want to learn more about how Ruby and Rails internals work, take a look at this article: How do gems work? Or, if you’re more interested in how the web works, check out A web server vs. an app server.

Pushing through tutorials, and still not learning anything?

Have you slogged through the same guide three times and still don't know how to build a real app?

In this free 7-day Rails course, you'll learn specific steps to start your own Rails apps — without giving up, and without being overwhelmed.

You'll also discover the fastest way to learn new Rails features with your 32-page sample of Practicing Rails: Learn Rails Without Being Overwhelmed.

Sign up below to get started:

Powered by ConvertKit

Did you like this article? You should read these:

Comments