Transcript
  • Web

Signing Cookies

From the class:  Session Cookies

After the last video, you might be left with an unsettled feeling that something might be wrong here, that there might be something insecure, and you'd be right. I mean, what prevents a user from just opening up a console, using the CURL command, and providing a cookie that is keyed on app session and the value is just some bogus value? Holy moly you might say.

Anybody can do this. They can just craft their own cookie. And that could have a user ID of some other user that's not even them. And so this is definitely an insecure system so far.

And if I press Enter here, it actually crashes the application because it's expecting a JSON object. And what he gets instead is this string. So it's not a very robust implementation.

But nonetheless, what we need to do is to prevent random users from sending up whatever cookie they want for the session. And to do that, we can use a technology called Signatures. Before we implement anything, let me open up Node.js and show you how signatures work by themselves so you get the hang of it. The methods we need are in the crypto module. So I'll include that or require it.

Now let's say we've got a value. A value is just going to be some kind of string. And when we get back to our cookie implementation, it's going to be a cookie value.

So I'll just say cookie value here. But it can be any string we want. And what we want to do is to come up with a unique key, a hash key for this value. To do that, I'm going to create something called an HMAC. It stands for Hash Message Authentication Code.

I can use a method of the crypto module called Create HMAC to do this. And just about any major programming language standard library will have something that will do this. And then I'm going to pass it the type of HMAC that we want to create.

In this case, it's going to be a hash function called sha256, and then a password of some kind. So it's going to be some kind of secret password, hopefully something that's longer than what I just typed here. And that's going to return an object that we can use to generate a key for this value.

To do that, I call the update method and pass the value as a parameter and then I want to get back a string. So I'm going to call the digest method and say encode the string in base 64. And what comes back is something called a Signature. It's just a string of characters that uniquely identifies this value here in combination with this password. So it's kind of like a hash function, except it's cryptographically secure because we're using this password to come up with the key.

So if someone else tries to sign this content and they use a different password, they're going to get a different result here, a different hash key. So we can use this to sign our cookies. And then when the cookie comes back, if anybody has tampered with it we can tell because we'll compute a key again and see whether the key is different from what has been sent up. And what we're doing is we're signing the content or adding a signature to this content.

Let's go implement it. As I write the code for this if you can't follow along completely, don't worry, just try to understand the concept. That we're signing some content and that way we can tell whether it's been tampered with when it comes back simply by signing it again seeing if the value is different. So let's create a function called Sign. And that's going to take our cookie value.

And we'll create some secret. This could be stored in a variable somewhere else. But just to make it easy, we'll call it our super duper secret.

And then we'll create a signature. I'll just create a variable called Signature and set it equal to crypto. And I'll do this on multiple lines so that's a little easier to read. HMAC. We'll use the sha256 library. And I'll pass the secret as a parameter. I'll update the HMAC with the value of our cookie. And then we'll get back a digest, which is encoded in base 64.

Now what I'm going to do is return the value with the signature appended at the end. And we need some way of extracting this later. So I'll use a dash dash. So it'll just be like a special way of differentiating between the value of the cookie and its signature.

So now we've taken the cookie value and we've signed it with a unique signature that only we can produce because we signed it with a secret key, the super duper secret. So as long as nobody has that secret, we're good to go. Now we need to create the corresponding unsigned methods. So this will-- we'll call it unsigned-- and given a signed value, we're going to unsign it.

And we'll go ahead and return false if the signatures don't match. So this is where we're going to do the signature check. First, let's split up the value based on that special delimiter that we created up here. Remember this dash dash.

In the first part of that is going to be the original value. So we'll say that's parts 0. We'll disregard the signature. We don't really care about the signature that was sent up. We're just going to create a new signature and then compare the new value with the value that was passed into this function as a parameter.

So let's create something called a Checked Value. And I'll go ahead and sign the original value again. So this will give me back a new string, again with the value that we pass in, the dash dash, and then a signature.

Then I can check whether or not the checked value is equal to the value that was passed in originally. And if it is, then just return the original value. Otherwise, we'll return false to indicate that the signatures didn't match.

Next, we need to update the getSession method and setSession method to make use of our new signature functions. In the getSession method if there's a cookie that's been provided to us by the client, the first thing we'll do is get the unsigned version of it. So we'll create a variable called unsigned. And let's set that equal to unsign.

And we'll pass into that request dot cookies app session. We're not going to JSON parse it yet. First, we're going to get the original value after it's been unsigned. So that's going to take the signature away and give us back the original value, otherwise it'll return false if the signatures don't match.

So we'll check whether or not the unsigned value is false or true. And if it's true, then we'll set the session here to the result of parsing the unsigned cookie. And if it's false, it must mean that the signatures didn't match. In that case, why don't we log an error that says something like cookie signature doesn't match. Let's say no match. And we can reset the session.

And if there was no cookie to begin with, we'll create a new session from scratch. Then down in the setSession method, all we need to do is to sign the JSON string before we encode it. And remember, the encoding part here encodes it into a URL or a string that's acceptable for a URL component. So that's what this is doing.

So what this is going to do is to encode URI component, the signed version of our JSON string. Back in the browser, I'll clear the cookies by using the right click and then pressing the Clear button and refresh the browser. And notice that we've got a value.

And in the middle there's that dash dash. On the left side of this dash dash is the encoded JSON value. And on the right side of it is the unique signature that only our server could have produced.

So if this value gets tampered with somehow, we'll recompute the signature on the server and see that it's not the correct signature and throw some kind of error. So this way we can prevent the cookie from being tampered with or a cookie from being created even in the first place. So only the cookie can be created on the server, not from the browser or some random user sending it up via CURL.

Let's make sure that the errors work properly by doing that. This time in CURL, I'll set a header, the cookie header by hand. And set app session equal to this bogus string with no signature at the end. And what's going to happen when I press Enter is the server will try to compute a signature itself for that string.

And it's not going to match. And so we get this message in the server logs the says the cookie signature doesn't match. And then the session gets reset to a new session as a result of that. And you can even see in the server response down below that the app session is set now to a new JSON encoded object.

This is the original object, by the way. And so that's down here. And then there's the dash dash. And on the right side of it is the signature that's computed on the server.

So if this signature is sent up on future requests it will match. And so we won't get this error. But the cookie signatures don't match. And we should be good to go.

So signatures prevents tampering of cookies from the client. It makes sure that only the server can create the cookie or update its values. And this example that we saw in this video was a little bit hard to follow.

There's a lot going on here. But hopefully you get the idea. So when you see the idea of signatures in whatever framework you're using, you'll know what it means.

Now, a signature prevents tampering but it doesn't encrypt the values are hide the values from the client from prying eyes. And so if we happen to leave a cookie on a browser in a coffee shop or something, other people will be able to read those values. So some frameworks will automatically also encrypt the cookie.

So if you used rails, for example, rails will automatically encrypt the cookie using a secret. We don't have time to cover that in this class. But you should be able to imagine that in addition to this signature, this entire value could be encrypted to be completely indecipherable.

And that way when you pass the cookie over an insecure HTTP connection, people can't sniff the results or read it. And if you pass it over HTTPS, they can't either. But they won't be able to see the cookie in the browser if they were to open up the JavaScript console either. So it'll be encrypted completely.