A Deep Dive Into Sessions
CC Attribution - ShareAlike 3.0 Unported:
You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal and non-commercial purpose as long as the work is attributed to the author in the manner specified by the author or licensor and the work or content is shared also in adapted form only under the conditions of this
Weiss, Justin
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? The session is the perfect place to put this kind of data. But sessions 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?
 I hope you're doing well. Thank you for coming by. I'm Justin Weiss and I work at AVO where we help people find the legal help that they need. At AVO, I help our software developers get better code into production more quickly. I also write articles and guides to becoming a better Rails developer on my site, justinweiss.com. And I wrote a book, Practicing Rails, which can help you learn Rails without getting overwhelmed. Hey, everyone. Hope your day has been going well so far. Thank you for coming by. I'm Justin Weiss and wave at me if you've seen this part already. What if that was your experience on the web? Like imagine if your site couldn't know that the same person visited two different times or if everything you knew about a user just disappeared as soon as you returned that first piece of HTML. Now this might be fine for a site that doesn't do much, you know, a site that only cares about handing out the most generic information. But most of us, we don't really live in that world, do we? We need to know about our users and we need to store some data about them, whether that's like a user ID, a preferred language, whether they like the mobile or the desktop version of your site better, or what their favorite read of cat to make memes with is. Now you could solve this the functional programmer way. You know, if you can't store a state, then pass all the data you have along with every single request. Whoops. But since we're all using Rails, this problem is pretty easy to solve. We put data in the session hash and it magically comes back to us on the next request. And with that, we never have to worry about which user is accessing our site ever again. So this has been my talk in intro into sessions. Thank you all for coming. But wait a second. How does this even work? How does it stick around? I mean, I thought HTTP was stateless. Now Rails makes using sessions really easy and that's great, but it's also a little bit dangerous. For a long time, I treated sessions like a database that I didn't have to set up. I didn't understand sessions, but I didn't really need to because they were a magic hash that I could always depend on. Sometimes depend on. And that sometimes, that flakiness, it meant that I hated using sessions. I mean, how many of you get unreasonably frustrated when session stuff doesn't work? When you see session exceptions or session bugs or missing data for a user. Why is that? In my first web programming job, a lot of people didn't have accounts and so we had to use sessions a lot. And I caused so many problems with them. No pointer and couldn't find data for this user exceptions. Those showed up like ten times as often as any other. And I actually got reprimanded more than once for the problems that I caused around sessions. So this is what I did. I didn't understand it, so I just did what I was already doing harder. The code I wrote wasn't working. The code I wrote wasn't working, but that's okay because I'll just write more code. All checks everywhere. Now, when that didn't work, I tried to avoid them. Sessions are terrible. Let's not use them. I have an idea. Let's just make the user log in on every page. It'll be great. But then I got a little bit more mature and I realized that this problem wasn't going to go away. So instead, I spent the time to really understand sessions at a deep level and construct a mental model out of them that worked. And after a while, I started to be able to write code that avoided a lot of these problems in the first place. The nice thing is, to understand sessions, we really don't need to get too complicated. Really, this is what we want. We want to know about a user. We want to know about them securely so that nothing can mess with that data. And we want to know about it until they leave. Once they stop using the site, we don't really need to keep that data around anymore. All we need is some way for your user's browser to coordinate with your Rails app and make everything connect up. Now this problem, the problem of not being able to keep track of information about users, like people realize this is a thing pretty early on. And as more people wanted to use the web, and especially to buy things on the web because money, people, developers needed a way to keep track of things like shopping carts and user preferences. Now I was joking a little bit earlier about passing in all the data you needed along with every request through the URL. But that's actually not too far off from what we ended up doing. If those params were in the URL, though, it's easy to see them, it's easy to lose them, and it's easy to fake them. You have another option, though. When a browser makes a request to a server, it sends some headers along with that request, some information about that request. So what if browsers sent some user data along with the rest of the headers so the server could see it? Once the server saw that data in the headers, it could use it, it could change it, and it could send new data back to the browser. Then the browser could send that modified copy back to the server, and they could just kind of ping-pong it back and forth, changing it as they needed to. Now this idea, the idea to use a header that would be automatically sent from the browser, this came out of Netscape in the early 90s, and Netscape called these special headers cookies. And about a year later, it was supported in IE, so, yeah, even 20 years ago, we had to do with that. But the neat thing about this is that the server really doesn't have to manage any of this data at all. This data is managed with or stored with the browser and managed by the browser. So how do these cookies work? Like, what do they look like? Well, let's say you make a request to Google through your browser, or because these are slides through a program like Curl. When Google sends you the page, it also sends you some HTTP headers, you know, like that metadata about a request. And there's one line I want you to focus on right now, this one. When Google returns a page, it also sends your browser that's a cookie header. When your browser sees that, it stores it along with information about which server it came from, in this case, Google.com. That's so that the next time you request a web page from Google, your browser will send headers like this. And if we put these side by side, you can see it sends that exact same cookie the next time your browser hits Google. That way, your browser and your server have some shared piece of information. They have that connection, they can keep their conversation going, and not have to reintroduce themselves to each other every single time. Now all cookies have a few different parts. They have data, which is the information that your server wants to remember. And they have metadata, which the browser cares about, which determines how and when that cookie should be sent to a server. This first part, the part before the semicolon, is the data. When you use the session object and rails, it's storing data in that part, and it's reading data out of that part. The rest of it is all metadata. For example, you can give cookies an expiration date. In this case, after September 7th, the browser won't send this cookie anymore, which means that the server won't have access to the data inside that cookie anymore. If you don't set an expires date, the cookie will usually disappear as soon as the browser closes. And these are called session cookies because they last for one browser session and then get deleted when you close the browser. They're sometimes even just stored in the browser memory and not actually persisted anywhere because you don't really need to. Cookies that have an expires date are sometimes called permanent cookies because they last until the date you set. They're not automatically cleared when you close the browser. Now sites can't read each other's cookie data, so if I was visiting goggles.com instead of Google.com, this cookie wouldn't be sent. With path and subdomain, you can go a little bit further. You can go down to subdirectories and subdomains, which is especially helpful if you end up running multiple apps run by multiple users on a single domain, like Think It Hub pages or WordPress. There's a leading dot there, but.google.com is the same thing as Google.com. It's safer to have it because older browsers still care about it. Now when you set a domain, it also includes all subdomains. So here the cookie is valid for Google.com and all the subdomains, drive.google, docs.google, all that stuff. And there's also some extra attributes you can give cookies like HTTP only that I'll go over a little bit later on. But you didn't just come here to learn about cookies and get hungry before lunch. What does all of this have to do with sessions? Well sessions are built using cookies because cookies are a pretty reliable way to keep track of users without having to keep track of params. So the better you understand cookies, if you can understand cookies at a deep level, it's going to be much, much, much easier to understand sessions and how they work and how they don't. We'll see in a minute that just like a hash, you can build way more complicated things on top of cookies. But out of the box, they're pretty limited. A single cookie can hold only a single value, so you need a separate cookie for every key value pair. And because all of the information is on the users side, on the browser side, you can't necessarily trust what they give you. Like what if you stored username inside a cookie? The server would send this to your browser, then you go into your browser's cookie database and change it to this. Your browser sends this cookie to the server and all of a sudden you can mess with everybody's data because the server doesn't know any better. Like it can't know any better. It has to trust this. Now this isn't any safer than having it in the URL like we saw before. So how does Rails get around these problems? Well, let's dig into an example. Here we have a pretty simple controller action. It takes whatever's in param's name, like whatever's passed as a name param, and puts it into the session under session name. This way we should be able to see some session data get created. So let's hit that and see what we get back. You can see that we're sending the name just into the controller. Again, our controller is going to take that name param, put it into the session, and then hopefully return that session back. And that's maybe what we see over there. You can see that Rails stores the session under a single key, like a single cookie. This session my app cookie. And if you search the sample code base for session my app, you'll find it in an initializer, session store.rb. If you change that option, it'll change which key your session data is stored under and also break all your old sessions in the process. So not necessarily a great idea, but you can certainly do it. Now what about the rest of the cookie? Like I said, we maybe have the data in there, and that's because we can't really tell. It's totally unreadable. I mean, somebody can try if they want, but it's probably not going to be too successful. So how does it do that? Why does Rails do that? Well, the why is pretty easy to answer. The value looks like that so that users or anybody else can't mess with their own cookies. In modern versions of Rails, session cookies are signed, and that means that if anybody tamper with them, they become invalid. And they're also encrypted so that nobody can even see the data stored inside them. But that's not going to stop us. Let's try to get into this cookie and see what's inside. Now all Rails apps have a secret key. These are these things that get constantly leaked onto GitHub. And the key is also used for all the encryption that Rails does, which is why it's such a disaster when it gets leaked onto GitHub. But that includes encrypting and signing cookies. And Rails gives you some defaults, or generates some defaults, secret keys for Dev and Test. You can generate new ones for production with rake secret. When you're at boots, Rails puts the secret key into the key generator here at rails.application.keygenerator. I don't bother writing any of this down, I have links later on. But you can see we use that key generator, that rails.application.keygenerator at the top to create some secrets. And then we use that secrets to create an encryptor object. And this encryptor object is the same kind of thing that we use to encrypt and decrypt cookies. So with this encryptor object we have at the bottom here, we should be able to decrypt our cookie. So we can paste that big giant string of encrypted text into this encryptor object, and this is what we get. It works like JSON, right? So why is that JSON? Well, it turns out you can also configure that. It's set inside another initializer, this cookies serializer. And you can also use the symbol Marshall, if you'd rather use Ruby Marshall. But most people just use JSON, it's the default. It's also even useful if you're like trying to share cookies between apps written in other languages, because every language understands JSON at this point. So what do we know now? We know that Rails stores all the session data inside a single cookie. We know that it does this by turning it into JSON, which gives us the opportunity to put multiple keys and values inside a single cookie. We know that Rails signs and encrypts the cookie, so you can't tamper with it or look at it. And we know that the session key in the serializer can both be configured to be something different if you want to. So now we can see that the cookie actually does contain that name param that we passed way back then. And what we should be able to see now is if we stop passing that param and we pass this cookie instead, we should be able to see this data come from the session data and not from the param. And so let's check that out. Here we pass that big blob of encrypted cookie back to Rails, and it should remember who we are without a parameter. You see, we dropped the parameter off of the URL at the bottom there. And that's what we see. And we didn't have to pass any params. This is exactly the sort of thing that a browser would be doing in this case to store that data across multiple requests. So step by step to kind of tie everything together. Your browser hits a server, and the server stores data into the session. Rails turns that session data into JSON. It encrypts the JSON, and it signs it, and then it sends the encrypted cookie back to the browser in that set cookie header. The browser then stores it along with the fact that it came from your Rails app. That's so that the next time you hit your Rails app, the browser will send that cookie back. Rails will verify and decrypt it and then turn it into that session hash that everybody can use. It's like params that are passed on every page, but managed automatically so that you don't have to think about it. Finally, Rails can change the data and send it back to the browser, which will overwrite the previous cookie, and they can just keep passing that data back and forth. But if passing cookies back and forth was all there was to sessions, there'd be no reason to call them sessions. I mean, you just say, hey, this cookie, this one is your session cookie. It's just the same thing. But cookies aren't always right. Remember how your browser sends that cookie along with every single request? Well, what happens if you start storing a ton of data inside of that cookie? What happens if you store a 4 meg PDF inside the cookie, or like the full text of Moby Dick for some reason? Every request to your server would include that 4 megs of data, even if your server didn't care about it right now, even if your server didn't read it during that request. So cookies are limited. You can only put 4 kilobytes of data in there. If you store more than that, you're going to get an exception. This action dispatch cookie's cookie overflow exception, which also happens to be the most delicious of all of the Rails exceptions. But even 4k is a whole lot bigger than most HTTP requests. I mean, most requests are only a couple hundred bytes. This is like 10 times that size. So if you care about performance, you probably don't want to get even close to that 4k limit. But what if you needed to store more data than that inside your session? How can you keep your cookies small, but make your sessions big? Well, let's think about how you're already dealing with users. If your cookie stored a user, you're probably storing a user ID in there. You're not storing their email address in there. You're not storing their full name in there. You're not storing their list of cart items in there. You're just storing their user ID, and then you'll use that ID to look up other information inside their database later on. But what about people who don't have an account? They don't have a user ID, so you can't do this. But you could generate a random ID, and you could store that in the cookie, like this. Then you could use that ID in the exact same way that you're using user ID to look up information from the database later on. It's not really a user ID, though. So you should probably call it something different, in this case, a session ID. So now we have two different options for storing data persistently across multiple requests. You can store the data right inside the cookie, or you can store a reference to that data inside the cookie and store the actual data someplace else, like inside a database. Now what would that second option look like? Well, let's say that, just like the rest of our data, we want to use ActiveRecord to store our session data. And let's say we called session name equals justin to create a brand new session. What would Rails have to do in order to store this in ActiveRecord? Well, Rails could generate a new random session ID, so it has something to look it up with. It could turn the session hash into a string so that you don't have to have, like, separate columns for everything you could possibly store in the session. You just stuff it on one string and then put it in a single column. It would save the ID and that data to a row in your database so you could look it up later. And then it would return the ID with setCookie so that the next time your browser hit your site, you could use that ID, look up your session data, and get your session hash back. So let's take a look at that in action. First we'll change our session store to the ActiveRecord store, which is a gem. Like that. Then we'll add some data to the session using curl again. Remember when we pass that name parameter, it just takes it out of the name, puts it into the session. And this time, we're just getting a short string returned instead of that big mess of encrypted signed data, that a6c49 string instead of, yeah, instead of that big mess. And that comes from, if you look inside your database, you'll see a session ID alongside some encoded data. And you'll notice that the session ID and the string return of the browser match. And that's how we'll use that ID to look up the session data later on. When your browser sends that cookie data back to the site, it remembers who we are, again without passing in a param. And this is how that works. It grabs the session ID out of the cookie, it looks up the session ID in your database, it pulls the data that's associated with that ID, and then it transforms that data back into your session hash. You can even store sessions in Memcache and Redis and MongoDB or like pretty much anywhere else. And they all pretty much follow the same process. Your cookie is now just a session ID, and then your app uses that ID to look up the rest of the information. And you can even write your own session store. You just need to tell Rack how to find sessions, how to create new sessions, write session data and delete sessions by implementing a couple methods. And Rails even includes a simple cache store that uses your Rails cache to store sessions. And it's a really, really simple and good example to follow if this is something that you're interested in. I'll have a link to that in the notes that I'll share at the end. And that's really the gist of how Rails stores sessions. There are two different strategies it uses. There's the cookie store strategy and there's the everything else strategy. No matter what, you're storing some data inside the cookie because you have to. It's the way that the browser keeps that relationship with the server. But while cookie storage stores all the data inside the cookie, the other methods just store reference to data inside that cookie. And then they can store the data however they want, like in database, on disk, in memory, wherever. But now we have a choice to make because we have a few different ways to store sessions. And this is an important choice to make because changing session stores is not an easy thing to do. So which one should you choose? Should you choose the cookie store, the cache store built into Rails, or the database store? Well, storing your session data in cookies is by far the easiest way to go. You don't need to do any extra infrastructure setup. It just kind of works out of the box. It's also nice because it syncs with your user lifecycle. And by that, I mean, while your user is visiting your site, it's active. When your user stops visiting your site, if they never visit your site again, you have no cleanup to do because the cookie is on the browser side, not the server side. No other methods can guarantee that and it saves you some cleanup. But it's also limited. You can only store four-cave data and you probably don't want to go anywhere near that. And it's also more vulnerable to certain kinds of attacks, which I'll go into a little bit later on. But if the cookie store won't work for you, you have two options. You can store sessions in a database or you can store them in your Rails cache. Now you might already be using something like memcache to cache your partials or some like API response data or that kind of thing. And if you are already using a Rails cache, then this is pretty easy too. It's already set up for you. You don't have to do any more extra infrastructure work or any of that kind of stuff. You also don't have to worry about your session growing out of control because most good cache is going to evict older stuff when new stuff comes in. And it's fast because if your cache is slow, you probably have bigger problems to solve. But it's also not perfect. Your sessions and your cache data are going to be fighting for space. And if you don't have enough memory, you could be facing a ton of early cache misses and early expired sessions. And if you ever need to reset your cache, like let's say you upgraded Rails or you made like a big sweeping change around your site and you just wanted to wipe everything and start over, you can't do that without also wiping your sessions. Still, this is how we tend to store data on our main Navi.com site and it's worked pretty well for us so far with those caveats. Now, if you want to keep your data around until it legitimately expires, you probably want to keep it in some sort of database, whether that's like Redis or whatever you're using for Active Record or something else. But storing sessions inside your database has some other problems. Sessions won't get cleaned up automatically. So you'll have to go through and delete old sessions on your own. You also have to know how your database is going to work when it's full of session data. Like are you using Redis as your session store? Is it going to try to keep all of your session data in memory? Does your server have enough memory for that or is it going to start swapping so hard that you can't SSH in to fix it? It's happening to me. You also have to be more careful about when you create session data or you'll fill your database with useless sessions. Like, for example, if you accidentally touch the session on every single request, when Googlebot crawls your site, they could be creating hundreds of thousands of useless sessions that are never going to be hit again. And that would be a bad time. So most of these problems don't happen super frequently, but they're all things that you need to think about if you're storing the session data semi-permanently. Now, if you're pretty sure you won't run into any of the cookie stores limits, cookie store is my favorite. You don't need to set it up, and it's not a headache to maintain. Cache versus database, I see as more of a choice of how much you want to do maintenance versus how much you worry about accidentally expiring sessions early. I tend to treat session data as pretty temporary. I tend to program pretty defensively around sessions. So the Cache Store works well for me. And my personal preference is cookie store first, then Cache Store, then Database Store. Now, in a lot of these examples, we've used sessions to identify users. And that's actually one of the more common things you're going to use sessions for. And that also makes it a super juicy target for hacking. That means on top of the pretty simple key value pair that make up cookies and sessions, there's a lot of extra stuff that somebody needs to worry about in order to keep your cookies secure. Now I talked about how the Rails server trusts your cookie. It kind of has to, because it's the only thing it has to go on. And that means that if somebody else can get your session cookie, the Rails server has no idea, no way to tell that they're not actually you. Now in lots of public Wi-Fi networks, you could pretty easily snoop on other people's network traffic. And so if you're sending cookies to an insecure, over an insecure network to insecure servers, somebody might be able to grab your cookies and pretend that they're their cookies. Now this became a pretty big deal a couple years ago when Guy Eric Butler released a proof of concept called Firesheep that would grab cookies over open Wi-Fi networks. And check this out. You'll click on somebody and you're instantly logged in as them. That's scary, right? I mean, Facebook used to have this happen to them. Now the only way to really prevent this is to run your site over HTTPS. And that way all of your cookie data and all your session data is secured along with the rest of your internet traffic. On the Rails side, you can turn this on pretty easily. There's some extra infrastructure setup you'll have to do though. But you flip this config for ssl equals true in your production.rb. And with free SSL certificates from Let's Encrypt and this whole ecosystem building up around them, and I think Heroku now supporting SSL on all paid dinos, there's really not a great excuse to run a site without SSL anymore. After you force SSL on, Rails will automatically add this attribute to your cookies, this secure attribute. What this means is that your cookies will no longer be sent to HTTP protocol sites. It's only going to be sent to HTTPS. It works just the same way as if you were trying to send a cookie to a different domain. But snooping a Wi-Fi connection isn't the only way to steal somebody's cookies. Because JavaScript can also read cookies. That is, if you're Google, you can use document.cookie to read Google.com cookies. And anybody else on Google that can run JavaScript on Google can also read Google cookies and send them to whatever server they want. Now MySpace is probably my favorite example of this. MySpace is my favorite example of a lot of things, but this was particularly fantastic. So that site used to have scripting vulnerabilities all over the place. It was really easy to get JavaScript or Flash embedded on your profile. And once you did that, you could grab information about any of the people that hit your page, like name, profile URL, account ID, all that kind of stuff. I'm guessing that you probably could have even logged in as them, but nobody that I knew figured that part out. Rails protects you from a lot of these attacks automatically by escaping your HTML. And Rails also marks session cookies as HTTP-only by default. What that means is that when a cookie is marked as HTTP-only, it's only going to be accessible to your server. The browser's not going to make it accessible to JavaScript anymore. So that helps with a lot of these things. But that's not enough. You can't even trust your own users. Like say you run a music store and your customers can earn credit to buy songs. Your boss read an article saying that forcing signups drops conversion rate, so no more signups, we're just going to put everything in the session instead. Seems great until one of your users gets this brilliant idea. But Justin, you probably say, we've already went over this. Rails encrypts and signs the cookie so you can't tamper with it. But in this case, you don't actually have to tamper with it. Imagine the cookie was encrypted. You couldn't actually get into this. Nobody buys a song sending this cookie with 400 credits into it. You respond with a new cookie that has 300 credits into it. Your user ignores that cookie and sends the old one again for 400 credits. Now they have infinite credits because it's never going to go down. Now this one doesn't really have an easy fix. You can store a unique number in the session and then check to make sure that you never use a number more than once, which is not really my favorite thing to do. Or you can switch to a database store or a cache store, which doesn't really have this problem because all of that data is drawn on the server side. But a better idea is just not to put this data in the cookie to begin with. That's what we have databases for, for storing this kind of data. Now these are some of the more interesting attacks, but there's a whole lot more. And I'm a big, big fan of the Rails security guide for learning more about this kind of thing. I'll have a link to that in the notes also. Now it might seem like there's a lot to think about around cookies and sessions, but there are a few good rules of thumb that I've picked up over the years that tend to keep problems to a minimum. The first is prepare for the session to disappear at any time. And this happens because sessions are on the user's computer. And that's a problem because that means you have absolutely no control over when they clear their cookies or when they switch devices or any of that stuff. So keep in mind every time you use a session, that session might not be there anymore. Program defensively because it's going to happen and if you're not prepared for it, it can cause big problems later on when you're not seeing the data that you expect. The second is don't store actual objects in your session. So why would this be a bad idea? Well, let's say you store a cart item in the session that has a title and a quantity. And later on, you rename title to name because, frankly, title is a terrible name for a cart item and I have no idea who came up with that. This is probably going to work great for you in Dev. You probably don't even have a cart item in your session in Dev mode. But then when you ship to production, cart items in old sessions are going to try to turn into new cart items. They'll try to put data into a title attribute that no longer exists. Then everything will explode. Now I've personally taken down large chunks of site because of this problem and I know I'm not the only one. I've seen this happen many times. And when it happens, you really only have two options and they're both terrible. One is you could reverse the change, which probably isn't going to work because now you have people with old cart items and new cart items in their sessions and so you could try to come up with some grand unified cart item that deals with both the whole time, the site's falling down and everybody's hair is on fire and it's just a bad time. Or you could just say we're going to start from a clean slate, we're going to wipe all the session data and we're going to log everybody out and lose all their data. So I usually go for this third one over here, at least for a little while. Now the bigger your objects are, the more likely this is to happen. This never shows up in Dev and Test because you probably aren't using sessions in the same way in Dev and Test as you are in production. It's the ultimate works on my machine and it will wreck everything when it ships. So just don't do it. For first, storing references to objects, object IDs in the session, not the objects themselves. And finally, be deliberate about what you use the session for. Only use the session when it makes a lot of sense. Because sessions are so easy, it's really easy for them to become a dumping ground of random data and that's when things start to go really wrong. One of the worst bugs I ever investigated started like this. We shipped something and we all of a sudden started to see exceptions coming from what seemed like a completely random part of the app. Like a lot of session bugs, we couldn't repro it locally and we couldn't debug it remotely. And after adding a bunch of logging, we ended up discovering that some code that we deleted a long time ago used the same name for something in the session as something that we just recently shipped. We had two completely different pieces of data that were stomping on each other and causing problems. And it turned out that neither of those things needed to be in the session. We just put it in the session because it was convenient and we didn't want to roll a whole new database table for them. And that convenience ended up costing us way more dev time and the experience of some of our users and it would have been just to do it right in the first place. Just like code, if you don't use sessions for something, it can't cause a problem. So use it with intent. Even when you follow these best practices, though, things are going to go wrong. They just will. So how do you start debugging when you're not seeing what you expect? Well, the best trick I've ever learned to help me debug any kind of problem is to isolate the problem area as quickly as possible. And by that I mean, is a function getting the right input? You probably don't need to go any higher than that. Is a function sending the right output you'd expect given its input? You probably don't need to go any lower than that. And you just keep cutting those closer and closer until you really narrow in on the place that's causing the problem. So the best tools I've found for debugging session issues are all about showing me what my server is sending and what my server is receiving. So how many of you are using something like Quirrell or Postman or Paw in your web development? Yeah, that's just many, many people. So these are great tools for debugging session issues. You can see the session data your server is sending and you can send arbitrary sessions back to the server and see how it responds. If Quirrell, Postman or Paw are telling you that your server is working OK, you can usually assume that it's a problem with the browser not sending something you expect or something else that's weird going on. And for debugging weird internet problems, MITM Proxy is my favorite tool. So with MITM Proxy, it's a little server that sits between your browser and your app and it shows you all of the network connections that go on between the two of them. So you can see like a list of network requests. You can dive into each of these things and start to see the request headers, the response headers, all that kind of thing. You can see all of the real stuff that's going back and forth, which is really great at helping to build these kinds of problems. And even just last week, I was debugging a session race condition where we had Ajax requests that were stomping on session data. And with MITM Proxy, I was able to, within about a half hour, construct an actual timeline of how these requests were going through when they were coming back and how they were conflicting with one another. So I'm a big, big fan of this tool. If your browser isn't sending cookies correctly, nine times out of ten, the domain settings on your cookie are wrong. This is really easy to mess up. It's also really hard to test in dev because in dev mode, you're probably not running the entire DNS structure of your production website. And it's also going to be hard to debug your session, your cookie data if you can't see what it is. So if you're using cookie sessions, I have the beginnings of a gem that you can install in dev mode. If you include the gem and run Rails console inside your Rails app, you can paste in your cookie strings and decrypt it using the Rails key generator. And I'll have links to all this stuff in the talk notes too. From all this, we can kind of see that sessions are core to the modern web. And by modern, I mean since like 1995, so modernish. And when you run into problems with session data, it might seem like they're big, they're complicated, they're flaky, they're frustrating. But like we saw, session data isn't that big of a thing. Sessions are based on a pretty simple primitive, you know, a single key, a single value, and some metadata. And on that foundation, you build new features bit by bit and piece by piece. Most you serialize the data so that you can store, or the values, so you can store more data into a single cookie. Then you encrypt it to avoid tampering with it. Or you use the cookie values as a reference to data somewhere else. Sessions get big and complicated, but really at their core, they're built out of a few simple parts that all can be combined together. And this is one of my favorite things about software development, is that it's all just code. And things that seem super complicated, like Git or Sessions or how the web even works, they were all built by somebody. They were built for a reason. They were built to solve a problem. And when you understand what that problem is and how they were built, they're all at their core understandable. We usually only dig into these things when we have problems with them, and that stress and that confusion, it makes these seem completely insurmountable. But in the end, they almost always turn out to be way more simple than you'd expect. So the next time you get unreasonably frustrated when something doesn't work, don't be like me, skip straight to that last phase, spend some time and force yourself to learn it. Turn that frustration into a mystery to solve and dig into the pieces until they're small enough to understand. Until you'll transform bugs that seem confusing, random and unfair, into some new and exciting piece of knowledge you can use for the rest of your programming career. And if you've recently learned something new and exciting or want to talk programming or really anything else, I would love to talk with you. My email address is up here. Use it. I love getting email. I read and respond to everything. Let me know if you're ever in Seattle. I'd love to grab a coffee with you. And that last link up there, if you're going to write one thing down from this, write that down. It's a link to resources for the talk with the slides, gem to decode, encrypted sessions, and some other notes that didn't quite fit in and some useful session-related links. So it looks like I have about eight minutes or so for some questions if anybody has them. I may have to answer them or deflect them in some creative way. All right. Well, thank you so much again for the time.
 I hope you're doing well.  Thank you for coming by.  I'm Justin Weiss and I work at AVO where we help people find the legal help that they  need.  At AVO, I help our software developers get better code into production more quickly.  I also write articles and guides to becoming a better Rails developer on my site, justinweiss.com.  And I wrote a book, Practicing Rails, which can help you learn Rails without getting overwhelmed.  Hey, everyone.  Hope your day has been going well so far.  Thank you for coming by.  I'm Justin Weiss and wave at me if you've seen this part already.  What if that was your experience on the web?  Like imagine if your site couldn't know that the same person visited two different times  or if everything you knew about a user just disappeared as soon as you returned that  first piece of HTML.  Now this might be fine for a site that doesn't do much, you know, a site that only cares  about handing out the most generic information.  But most of us, we don't really live in that world, do we?  We need to know about our users and we need to store some data about them, whether that's  like a user ID, a preferred language, whether they like the mobile or the desktop version  of your site better, or what their favorite read of cat to make memes with is.  Now you could solve this the functional programmer way.  You know, if you can't store a state, then pass all the data you have along with every  single request.  Whoops.  But since we're all using Rails, this problem is pretty easy to solve.  We put data in the session hash and it magically comes back to us on the next request.  And with that, we never have to worry about which user is accessing our site ever again.  So this has been my talk in intro into sessions.  Thank you all for coming.  But wait a second.  How does this even work?  How does it stick around?  I mean, I thought HTTP was stateless.  Now Rails makes using sessions really easy and that's great, but it's also a little bit  dangerous.  For a long time, I treated sessions like a database that I didn't have to set up.  I didn't understand sessions, but I didn't really need to because they were a magic hash  that I could always depend on.  Sometimes depend on.  And that sometimes, that flakiness, it meant that I hated using sessions.  I mean, how many of you get unreasonably frustrated when session stuff doesn't work?  When you see session exceptions or session bugs or missing data for a user.  Why is that?  In my first web programming job, a lot of people didn't have accounts and so we had  to use sessions a lot.  And I caused so many problems with them.  No pointer and couldn't find data for this user exceptions.  Those showed up like ten times as often as any other.  And I actually got reprimanded more than once for the problems that I caused around sessions.  So this is what I did.  I didn't understand it, so I just did what I was already doing harder.  The code I wrote wasn't working.  The code I wrote wasn't working, but that's okay because I'll just write more code.  All checks everywhere.  Now, when that didn't work, I tried to avoid them.  Sessions are terrible.  Let's not use them.  I have an idea.  Let's just make the user log in on every page.  It'll be great.  But then I got a little bit more mature and I realized that this problem wasn't going  to go away.  So instead, I spent the time to really understand sessions at a deep level and construct a mental  model out of them that worked.  And after a while, I started to be able to write code that avoided a lot of these problems  in the first place.  The nice thing is, to understand sessions, we really don't need to get too complicated.  Really, this is what we want.  We want to know about a user.  We want to know about them securely so that nothing can mess with that data.  And we want to know about it until they leave.  Once they stop using the site, we don't really need to keep that data around anymore.  All we need is some way for your user's browser to coordinate with your Rails app and make  everything connect up.  Now this problem, the problem of not being able to keep track of information about users,  like people realize this is a thing pretty early on.  And as more people wanted to use the web, and especially to buy things on the web because  money, people, developers needed a way to keep track of things like shopping carts and  user preferences.  Now I was joking a little bit earlier about passing in all the data you needed along with  every request through the URL.  But that's actually not too far off from what we ended up doing.  If those params were in the URL, though, it's easy to see them, it's easy to lose them,  and it's easy to fake them.  You have another option, though.  When a browser makes a request to a server, it sends some headers along with that request,  some information about that request.  So what if browsers sent some user data along with the rest of the headers so the server  could see it?  Once the server saw that data in the headers, it could use it, it could change it, and it  could send new data back to the browser.  Then the browser could send that modified copy back to the server, and they could just  kind of ping-pong it back and forth, changing it as they needed to.  Now this idea, the idea to use a header that would be automatically sent from the browser,  this came out of Netscape in the early 90s, and Netscape called these special headers  cookies.  And about a year later, it was supported in IE, so, yeah, even 20 years ago, we had to  do with that.  But the neat thing about this is that the server really doesn't have to manage any of  this data at all.  This data is managed with or stored with the browser and managed by the browser.  So how do these cookies work?  Like, what do they look like?  Well, let's say you make a request to Google through your browser, or because these are  slides through a program like Curl.  When Google sends you the page, it also sends you some HTTP headers, you know, like that  metadata about a request.  And there's one line I want you to focus on right now, this one.  When Google returns a page, it also sends your browser that's a cookie header.  When your browser sees that, it stores it along with information about which server it  came from, in this case, Google.com.  That's so that the next time you request a web page from Google, your browser will send  headers like this.  And if we put these side by side, you can see it sends that exact same cookie the next  time your browser hits Google.  That way, your browser and your server have some shared piece of information.  They have that connection, they can keep their conversation going, and not have to reintroduce  themselves to each other every single time.  Now all cookies have a few different parts.  They have data, which is the information that your server wants to remember.  And they have metadata, which the browser cares about, which determines how and when  that cookie should be sent to a server.  This first part, the part before the semicolon, is the data.  When you use the session object and rails, it's storing data in that part, and it's reading  data out of that part.  The rest of it is all metadata.  For example, you can give cookies an expiration date.  In this case, after September 7th, the browser won't send this cookie anymore, which means  that the server won't have access to the data inside that cookie anymore.  If you don't set an expires date, the cookie will usually disappear as soon as the browser  closes.  And these are called session cookies because they last for one browser session and then  get deleted when you close the browser.  They're sometimes even just stored in the browser memory and not actually persisted  anywhere because you don't really need to.  Cookies that have an expires date are sometimes called permanent cookies because they last  until the date you set.  They're not automatically cleared when you close the browser.  Now sites can't read each other's cookie data, so if I was visiting goggles.com instead  of Google.com, this cookie wouldn't be sent.  With path and subdomain, you can go a little bit further.  You can go down to subdirectories and subdomains, which is especially helpful if you end up  running multiple apps run by multiple users on a single domain, like Think It Hub pages  or WordPress.  There's a leading dot there, but.google.com is the same thing as Google.com.  It's safer to have it because older browsers still care about it.  Now when you set a domain, it also includes all subdomains.  So here the cookie is valid for Google.com and all the subdomains, drive.google, docs.google,  all that stuff.  And there's also some extra attributes you can give cookies like HTTP only that I'll  go over a little bit later on.  But you didn't just come here to learn about cookies and get hungry before lunch.  What does all of this have to do with sessions?  Well sessions are built using cookies because cookies are a pretty reliable way to keep  track of users without having to keep track of params.  So the better you understand cookies, if you can understand cookies at a deep level, it's  going to be much, much, much easier to understand sessions and how they work and how they don't.  We'll see in a minute that just like a hash, you can build way more complicated things  on top of cookies.  But out of the box, they're pretty limited.  A single cookie can hold only a single value, so you need a separate cookie for every key  value pair.  And because all of the information is on the users side, on the browser side, you can't  necessarily trust what they give you.  Like what if you stored username inside a cookie?  The server would send this to your browser, then you go into your browser's cookie database  and change it to this.  Your browser sends this cookie to the server and all of a sudden you can mess with everybody's  data because the server doesn't know any better.  Like it can't know any better.  It has to trust this.  Now this isn't any safer than having it in the URL like we saw before.  So how does Rails get around these problems?  Well, let's dig into an example.  Here we have a pretty simple controller action.  It takes whatever's in param's name, like whatever's passed as a name param, and puts  it into the session under session name.  This way we should be able to see some session data get created.  So let's hit that and see what we get back.  You can see that we're sending the name just into the controller.  Again, our controller is going to take that name param, put it into the session, and then  hopefully return that session back.  And that's maybe what we see over there.  You can see that Rails stores the session under a single key, like a single cookie.  This session my app cookie.  And if you search the sample code base for session my app, you'll find it in an initializer,  session store.rb.  If you change that option, it'll change which key your session data is stored under and  also break all your old sessions in the process.  So not necessarily a great idea, but you can certainly do it.  Now what about the rest of the cookie?  Like I said, we maybe have the data in there, and that's because we can't really tell.  It's totally unreadable.  I mean, somebody can try if they want, but it's probably not going to be too successful.  So how does it do that?  Why does Rails do that?  Well, the why is pretty easy to answer.  The value looks like that so that users or anybody else can't mess with their own cookies.  In modern versions of Rails, session cookies are signed, and that means that if anybody  tamper with them, they become invalid.  And they're also encrypted so that nobody can even see the data stored inside them.  But that's not going to stop us.  Let's try to get into this cookie and see what's inside.  Now all Rails apps have a secret key.  These are these things that get constantly leaked onto GitHub.  And the key is also used for all the encryption that Rails does, which is why it's such a  disaster when it gets leaked onto GitHub.  But that includes encrypting and signing cookies.  And Rails gives you some defaults, or generates some defaults, secret keys for Dev and Test.  You can generate new ones for production with rake secret.  When you're at boots, Rails puts the secret key into the key generator here at rails.application.keygenerator.  I don't bother writing any of this down, I have links later on.  But you can see we use that key generator, that rails.application.keygenerator at the  top to create some secrets.  And then we use that secrets to create an encryptor object.  And this encryptor object is the same kind of thing that we use to encrypt and decrypt  cookies.  So with this encryptor object we have at the bottom here, we should be able to decrypt  our cookie.  So we can paste that big giant string of encrypted text into this encryptor object, and this  is what we get.  It works like JSON, right?  So why is that JSON?  Well, it turns out you can also configure that.  It's set inside another initializer, this cookies serializer.  And you can also use the symbol Marshall, if you'd rather use Ruby Marshall.  But most people just use JSON, it's the default.  It's also even useful if you're like trying to share cookies between apps written in other  languages, because every language understands JSON at this point.  So what do we know now?  We know that Rails stores all the session data inside a single cookie.  We know that it does this by turning it into JSON, which gives us the opportunity to put  multiple keys and values inside a single cookie.  We know that Rails signs and encrypts the cookie, so you can't tamper with it or look  at it.  And we know that the session key in the serializer can both be configured to be something different  if you want to.  So now we can see that the cookie actually does contain that name param that we passed  way back then.  And what we should be able to see now is if we stop passing that param and we pass this  cookie instead, we should be able to see this data come from the session data and not from  the param.  And so let's check that out.  Here we pass that big blob of encrypted cookie back to Rails, and it should remember who  we are without a parameter.  You see, we dropped the parameter off of the URL at the bottom there.  And that's what we see.  And we didn't have to pass any params.  This is exactly the sort of thing that a browser would be doing in this case to store  that data across multiple requests.  So step by step to kind of tie everything together.  Your browser hits a server, and the server stores data into the session.  Rails turns that session data into JSON.  It encrypts the JSON, and it signs it, and then it sends the encrypted cookie back to  the browser in that set cookie header.  The browser then stores it along with the fact that it came from your Rails app.  That's so that the next time you hit your Rails app, the browser will send that cookie  back.  Rails will verify and decrypt it and then turn it into that session hash that everybody  can use.  It's like params that are passed on every page, but managed automatically so that you  don't have to think about it.  Finally, Rails can change the data and send it back to the browser, which will overwrite  the previous cookie, and they can just keep passing that data back and forth.  But if passing cookies back and forth was all there was to sessions, there'd be no reason  to call them sessions.  I mean, you just say, hey, this cookie, this one is your session cookie.  It's just the same thing.  But cookies aren't always right.  Remember how your browser sends that cookie along with every single request?  Well, what happens if you start storing a ton of data inside of that cookie?  What happens if you store a 4 meg PDF inside the cookie, or like the full text of Moby  Dick for some reason?  Every request to your server would include that 4 megs of data, even if your server didn't  care about it right now, even if your server didn't read it during that request.  So cookies are limited.  You can only put 4 kilobytes of data in there.  If you store more than that, you're going to get an exception.  This action dispatch cookie's cookie overflow exception, which also happens to be the most  delicious of all of the Rails exceptions.  But even 4k is a whole lot bigger than most HTTP requests.  I mean, most requests are only a couple hundred bytes.  This is like 10 times that size.  So if you care about performance, you probably don't want to get even close to that 4k limit.  But what if you needed to store more data than that inside your session?  How can you keep your cookies small, but make your sessions big?  Well, let's think about how you're already dealing with users.  If your cookie stored a user, you're probably storing a user ID in there.  You're not storing their email address in there.  You're not storing their full name in there.  You're not storing their list of cart items in there.  You're just storing their user ID, and then you'll use that ID to look up other information  inside their database later on.  But what about people who don't have an account?  They don't have a user ID, so you can't do this.  But you could generate a random ID, and you could store that in the cookie, like this.  Then you could use that ID in the exact same way that you're using user ID to look up information  from the database later on.  It's not really a user ID, though.  So you should probably call it something different, in this case, a session ID.  So now we have two different options for storing data persistently across multiple requests.  You can store the data right inside the cookie, or you can store a reference to that data  inside the cookie and store the actual data someplace else, like inside a database.  Now what would that second option look like?  Well, let's say that, just like the rest of our data, we want to use ActiveRecord to  store our session data.  And let's say we called session name equals justin to create a brand new session.  What would Rails have to do in order to store this in ActiveRecord?  Well, Rails could generate a new random session ID, so it has something to look it up with.  It could turn the session hash into a string so that you don't have to have, like, separate  columns for everything you could possibly store in the session.  You just stuff it on one string and then put it in a single column.  It would save the ID and that data to a row in your database so you could look it up later.  And then it would return the ID with setCookie so that the next time your browser hit your  site, you could use that ID, look up your session data, and get your session hash back.  So let's take a look at that in action.  First we'll change our session store to the ActiveRecord store, which is a gem.  Like that.  Then we'll add some data to the session using curl again.  Remember when we pass that name parameter, it just takes it out of the name, puts it  into the session.  And this time, we're just getting a short string returned instead of that big mess of  encrypted signed data, that a6c49 string instead of, yeah, instead of that big mess.  And that comes from, if you look inside your database, you'll see a session ID alongside  some encoded data.  And you'll notice that the session ID and the string return of the browser match.  And that's how we'll use that ID to look up the session data later on.  When your browser sends that cookie data back to the site, it remembers who we are, again  without passing in a param.  And this is how that works.  It grabs the session ID out of the cookie, it looks up the session ID in your database,  it pulls the data that's associated with that ID, and then it transforms that data back  into your session hash.  You can even store sessions in Memcache and Redis and MongoDB or like pretty much anywhere  else.  And they all pretty much follow the same process.  Your cookie is now just a session ID, and then your app uses that ID to look up the  rest of the information.  And you can even write your own session store.  You just need to tell Rack how to find sessions, how to create new sessions, write session data  and delete sessions by implementing a couple methods.  And Rails even includes a simple cache store that uses your Rails cache to store sessions.  And it's a really, really simple and good example to follow if this is something that  you're interested in.  I'll have a link to that in the notes that I'll share at the end.  And that's really the gist of how Rails stores sessions.  There are two different strategies it uses.  There's the cookie store strategy and there's the everything else strategy.  No matter what, you're storing some data inside the cookie because you have to.  It's the way that the browser keeps that relationship with the server.  But while cookie storage stores all the data inside the cookie, the other methods just  store reference to data inside that cookie.  And then they can store the data however they want, like in database, on disk, in memory,  wherever.  But now we have a choice to make because we have a few different ways to store sessions.  And this is an important choice to make because changing session stores is not an easy thing  to do.  So which one should you choose?  Should you choose the cookie store, the cache store built into Rails, or the database store?  Well, storing your session data in cookies is by far the easiest way to go.  You don't need to do any extra infrastructure setup.  It just kind of works out of the box.  It's also nice because it syncs with your user lifecycle.  And by that, I mean, while your user is visiting your site, it's active.  When your user stops visiting your site, if they never visit your site again, you have  no cleanup to do because the cookie is on the browser side, not the server side.  No other methods can guarantee that and it saves you some cleanup.  But it's also limited.  You can only store four-cave data and you probably don't want to go anywhere near that.  And it's also more vulnerable to certain kinds of attacks, which I'll go into a little bit  later on.  But if the cookie store won't work for you, you have two options.  You can store sessions in a database or you can store them in your Rails cache.  Now you might already be using something like memcache to cache your partials or some like  API response data or that kind of thing.  And if you are already using a Rails cache, then this is pretty easy too.  It's already set up for you.  You don't have to do any more extra infrastructure work or any of that kind of stuff.  You also don't have to worry about your session growing out of control because most good cache  is going to evict older stuff when new stuff comes in.  And it's fast because if your cache is slow, you probably have bigger problems to solve.  But it's also not perfect.  Your sessions and your cache data are going to be fighting for space.  And if you don't have enough memory, you could be facing a ton of early cache misses and  early expired sessions.  And if you ever need to reset your cache, like let's say you upgraded Rails or you made  like a big sweeping change around your site and you just wanted to wipe everything and  start over, you can't do that without also wiping your sessions.  Still, this is how we tend to store data on our main Navi.com site and it's worked pretty  well for us so far with those caveats.  Now, if you want to keep your data around until it legitimately expires, you probably  want to keep it in some sort of database, whether that's like Redis or whatever you're  using for Active Record or something else.  But storing sessions inside your database has some other problems.  Sessions won't get cleaned up automatically.  So you'll have to go through and delete old sessions on your own.  You also have to know how your database is going to work when it's full of session data.  Like are you using Redis as your session store?  Is it going to try to keep all of your session data in memory?  Does your server have enough memory for that or is it going to start swapping so hard that  you can't SSH in to fix it?  It's happening to me.  You also have to be more careful about when you create session data or you'll fill your  database with useless sessions.  Like, for example, if you accidentally touch the session on every single request, when  Googlebot crawls your site, they could be creating hundreds of thousands of useless sessions  that are never going to be hit again.  And that would be a bad time.  So most of these problems don't happen super frequently, but they're all things that you  need to think about if you're storing the session data semi-permanently.  Now, if you're pretty sure you won't run into any of the cookie stores limits, cookie store  is my favorite.  You don't need to set it up, and it's not a headache to maintain.  Cache versus database, I see as more of a choice of how much you want to do maintenance versus  how much you worry about accidentally expiring sessions early.  I tend to treat session data as pretty temporary.  I tend to program pretty defensively around sessions.  So the Cache Store works well for me.  And my personal preference is cookie store first, then Cache Store, then Database Store.  Now, in a lot of these examples, we've used sessions to identify users.  And that's actually one of the more common things you're going to use sessions for.  And that also makes it a super juicy target for hacking.  That means on top of the pretty simple key value pair that make up cookies and sessions,  there's a lot of extra stuff that somebody needs to worry about in order to keep your  cookies secure.  Now I talked about how the Rails server trusts your cookie.  It kind of has to, because it's the only thing it has to go on.  And that means that if somebody else can get your session cookie, the Rails server has  no idea, no way to tell that they're not actually you.  Now in lots of public Wi-Fi networks, you could pretty easily snoop on other people's  network traffic.  And so if you're sending cookies to an insecure, over an insecure network to insecure servers,  somebody might be able to grab your cookies and pretend that they're their cookies.  Now this became a pretty big deal a couple years ago when Guy Eric Butler released a  proof of concept called Firesheep that would grab cookies over open Wi-Fi networks.  And check this out.  You'll click on somebody and you're instantly logged in as them.  That's scary, right?  I mean, Facebook used to have this happen to them.  Now the only way to really prevent this is to run your site over HTTPS.  And that way all of your cookie data and all your session data is secured along with the  rest of your internet traffic.  On the Rails side, you can turn this on pretty easily.  There's some extra infrastructure setup you'll have to do though.  But you flip this config for ssl equals true in your production.rb.  And with free SSL certificates from Let's Encrypt and this whole ecosystem building up around  them, and I think Heroku now supporting SSL on all paid dinos, there's really not a great  excuse to run a site without SSL anymore.  After you force SSL on, Rails will automatically add this attribute to your cookies, this secure  attribute.  What this means is that your cookies will no longer be sent to HTTP protocol sites.  It's only going to be sent to HTTPS.  It works just the same way as if you were trying to send a cookie to a different domain.  But snooping a Wi-Fi connection isn't the only way to steal somebody's cookies.  Because JavaScript can also read cookies.  That is, if you're Google, you can use document.cookie to read Google.com cookies.  And anybody else on Google that can run JavaScript on Google can also read Google cookies and  send them to whatever server they want.  Now MySpace is probably my favorite example of this.  MySpace is my favorite example of a lot of things, but this was particularly fantastic.  So that site used to have scripting vulnerabilities all over the place.  It was really easy to get JavaScript or Flash embedded on your profile.  And once you did that, you could grab information about any of the people that hit your page,  like name, profile URL, account ID, all that kind of stuff.  I'm guessing that you probably could have even logged in as them, but nobody that I  knew figured that part out.  Rails protects you from a lot of these attacks automatically by escaping your HTML.  And Rails also marks session cookies as HTTP-only by default.  What that means is that when a cookie is marked as HTTP-only, it's only going to be accessible  to your server.  The browser's not going to make it accessible to JavaScript anymore.  So that helps with a lot of these things.  But that's not enough.  You can't even trust your own users.  Like say you run a music store and your customers can earn credit to buy songs.  Your boss read an article saying that forcing signups drops conversion rate, so no more  signups, we're just going to put everything in the session instead.  Seems great until one of your users gets this brilliant idea.  But Justin, you probably say, we've already went over this.  Rails encrypts and signs the cookie so you can't tamper with it.  But in this case, you don't actually have to tamper with it.  Imagine the cookie was encrypted.  You couldn't actually get into this.  Nobody buys a song sending this cookie with 400 credits into it.  You respond with a new cookie that has 300 credits into it.  Your user ignores that cookie and sends the old one again for 400 credits.  Now they have infinite credits because it's never going to go down.  Now this one doesn't really have an easy fix.  You can store a unique number in the session and then check to make sure that you never  use a number more than once, which is not really my favorite thing to do.  Or you can switch to a database store or a cache store, which doesn't really have this  problem because all of that data is drawn on the server side.  But a better idea is just not to put this data in the cookie to begin with.  That's what we have databases for, for storing this kind of data.  Now these are some of the more interesting attacks, but there's a whole lot more.  And I'm a big, big fan of the Rails security guide for learning more about this kind of  thing.  I'll have a link to that in the notes also.  Now it might seem like there's a lot to think about around cookies and sessions, but there  are a few good rules of thumb that I've picked up over the years that tend to keep problems  to a minimum.  The first is prepare for the session to disappear at any time.  And this happens because sessions are on the user's computer.  And that's a problem because that means you have absolutely no control over when they  clear their cookies or when they switch devices or any of that stuff.  So keep in mind every time you use a session, that session might not be there anymore.  Program defensively because it's going to happen and if you're not prepared for it,  it can cause big problems later on when you're not seeing the data that you expect.  The second is don't store actual objects in your session.  So why would this be a bad idea?  Well, let's say you store a cart item in the session that has a title and a quantity.  And later on, you rename title to name because, frankly, title is a terrible name for a cart  item and I have no idea who came up with that.  This is probably going to work great for you in Dev.  You probably don't even have a cart item in your session in Dev mode.  But then when you ship to production, cart items in old sessions are going to try to  turn into new cart items.  They'll try to put data into a title attribute that no longer exists.  Then everything will explode.  Now I've personally taken down large chunks of site because of this problem and I know  I'm not the only one.  I've seen this happen many times.  And when it happens, you really only have two options and they're both terrible.  One is you could reverse the change, which probably isn't going to work because now you  have people with old cart items and new cart items in their sessions and so you could try  to come up with some grand unified cart item that deals with both the whole time, the site's  falling down and everybody's hair is on fire and it's just a bad time.  Or you could just say we're going to start from a clean slate, we're going to wipe all  the session data and we're going to log everybody out and lose all their data.  So I usually go for this third one over here, at least for a little while.  Now the bigger your objects are, the more likely this is to happen.  This never shows up in Dev and Test because you probably aren't using sessions in the  same way in Dev and Test as you are in production.  It's the ultimate works on my machine and it will wreck everything when it ships.  So just don't do it.  For first, storing references to objects, object IDs in the session, not the objects themselves.  And finally, be deliberate about what you use the session for.  Only use the session when it makes a lot of sense.  Because sessions are so easy, it's really easy for them to become a dumping ground of random  data and that's when things start to go really wrong.  One of the worst bugs I ever investigated started like this.  We shipped something and we all of a sudden started to see exceptions coming from what  seemed like a completely random part of the app.  Like a lot of session bugs, we couldn't repro it locally and we couldn't debug it remotely.  And after adding a bunch of logging, we ended up discovering that some code that we deleted  a long time ago used the same name for something in the session as something that we just recently  shipped.  We had two completely different pieces of data that were stomping on each other and causing  problems.  And it turned out that neither of those things needed to be in the session.  We just put it in the session because it was convenient and we didn't want to roll a whole  new database table for them.  And that convenience ended up costing us way more dev time and the experience of some of  our users and it would have been just to do it right in the first place.  Just like code, if you don't use sessions for something, it can't cause a problem.  So use it with intent.  Even when you follow these best practices, though, things are going to go wrong.  They just will.  So how do you start debugging when you're not seeing what you expect?  Well, the best trick I've ever learned to help me debug any kind of problem is to isolate  the problem area as quickly as possible.  And by that I mean, is a function getting the right input?  You probably don't need to go any higher than that.  Is a function sending the right output you'd expect given its input?  You probably don't need to go any lower than that.  And you just keep cutting those closer and closer until you really narrow in on the place  that's causing the problem.  So the best tools I've found for debugging session issues are all about showing me what  my server is sending and what my server is receiving.  So how many of you are using something like Quirrell or Postman or Paw in your web development?  Yeah, that's just many, many people.  So these are great tools for debugging session issues.  You can see the session data your server is sending and you can send arbitrary sessions  back to the server and see how it responds.  If Quirrell, Postman or Paw are telling you that your server is working OK, you can usually  assume that it's a problem with the browser not sending something you expect or something  else that's weird going on.  And for debugging weird internet problems, MITM Proxy is my favorite tool.  So with MITM Proxy, it's a little server that sits between your browser and your app and  it shows you all of the network connections that go on between the two of them.  So you can see like a list of network requests.  You can dive into each of these things and start to see the request headers, the response  headers, all that kind of thing.  You can see all of the real stuff that's going back and forth, which is really great  at helping to build these kinds of problems.  And even just last week, I was debugging a session race condition where we had Ajax  requests that were stomping on session data.  And with MITM Proxy, I was able to, within about a half hour, construct an actual timeline  of how these requests were going through when they were coming back and how they were conflicting  with one another.  So I'm a big, big fan of this tool.  If your browser isn't sending cookies correctly, nine times out of ten, the domain settings  on your cookie are wrong.  This is really easy to mess up.  It's also really hard to test in dev because in dev mode, you're probably not running the  entire DNS structure of your production website.  And it's also going to be hard to debug your session, your cookie data if you can't see  what it is.  So if you're using cookie sessions, I have the beginnings of a gem that you can install  in dev mode.  If you include the gem and run Rails console inside your Rails app, you can paste in your  cookie strings and decrypt it using the Rails key generator.  And I'll have links to all this stuff in the talk notes too.  From all this, we can kind of see that sessions are core to the modern web.  And by modern, I mean since like 1995, so modernish.  And when you run into problems with session data, it might seem like they're big, they're  complicated, they're flaky, they're frustrating.  But like we saw, session data isn't that big of a thing.  Sessions are based on a pretty simple primitive, you know, a single key, a single value, and  some metadata.  And on that foundation, you build new features bit by bit and piece by piece.  Most you serialize the data so that you can store, or the values, so you can store more  data into a single cookie.  Then you encrypt it to avoid tampering with it.  Or you use the cookie values as a reference to data somewhere else.  Sessions get big and complicated, but really at their core, they're built out of a few  simple parts that all can be combined together.  And this is one of my favorite things about software development, is that it's all just  code.  And things that seem super complicated, like Git or Sessions or how the web even works,  they were all built by somebody.  They were built for a reason.  They were built to solve a problem.  And when you understand what that problem is and how they were built, they're all at  their core understandable.  We usually only dig into these things when we have problems with them, and that stress  and that confusion, it makes these seem completely insurmountable.  But in the end, they almost always turn out to be way more simple than you'd expect.  So the next time you get unreasonably frustrated when something doesn't work, don't be like  me, skip straight to that last phase, spend some time and force yourself to learn it.  Turn that frustration into a mystery to solve and dig into the pieces until they're small  enough to understand.  Until you'll transform bugs that seem confusing, random and unfair, into some new and exciting  piece of knowledge you can use for the rest of your programming career.  And if you've recently learned something new and exciting or want to talk programming  or really anything else, I would love to talk with you.  My email address is up here.  Use it.  I love getting email.  I read and respond to everything.  Let me know if you're ever in Seattle.  I'd love to grab a coffee with you.  And that last link up there, if you're going to write one thing down from this, write that  down.  It's a link to resources for the talk with the slides, gem to decode, encrypted sessions,  and some other notes that didn't quite fit in and some useful session-related links.  So it looks like I have about eight minutes or so for some questions if anybody has them.  I may have to answer them or deflect them in some creative way.  All right.  Well, thank you so much again for the time.
A_Deep_Dive_Into_Sessions_2_0_2015.png A_Deep_Dive_Into_Sessions_2_2016_3011.png A_Deep_Dive_Into_Sessions_2_3012_3119.png A_Deep_Dive_Into_Sessions_2_3120_3251.png A_Deep_Dive_Into_Sessions_2_3252_5075.png A_Deep_Dive_Into_Sessions_2_5076_5939.png A_Deep_Dive_Into_Sessions_2_5940_9167.png A_Deep_Dive_Into_Sessions_2_9168_10223.png A_Deep_Dive_Into_Sessions_2_10224_11327.png A_Deep_Dive_Into_Sessions_2_11328_17747.png A_Deep_Dive_Into_Sessions_2_17748_19751.png A_Deep_Dive_Into_Sessions_2_19752_19799.png A_Deep_Dive_Into_Sessions_2_19800_20591.png A_Deep_Dive_Into_Sessions_2_20592_26567.png A_Deep_Dive_Into_Sessions_2_26568_26699.png A_Deep_Dive_Into_Sessions_2_26700_32291.png A_Deep_Dive_Into_Sessions_2_32292_32687.png A_Deep_Dive_Into_Sessions_2_32688_32879.png A_Deep_Dive_Into_Sessions_2_32880_33107.png A_Deep_Dive_Into_Sessions_2_33108_33659.png A_Deep_Dive_Into_Sessions_2_33660_33875.png A_Deep_Dive_Into_Sessions_2_33876_35195.png A_Deep_Dive_Into_Sessions_2_35196_36311.png A_Deep_Dive_Into_Sessions_2_36312_36695.png A_Deep_Dive_Into_Sessions_2_36696_36791.png A_Deep_Dive_Into_Sessions_2_36792_37907.png A_Deep_Dive_Into_Sessions_2_37908_38087.png A_Deep_Dive_Into_Sessions_2_38088_38375.png A_Deep_Dive_Into_Sessions_2_38376_41075.png A_Deep_Dive_Into_Sessions_2_41076_49019.png A_Deep_Dive_Into_Sessions_2_49020_49040.png