Hacker Time
Presented by: Evan Johnson
Originally aired on October 19, 2023 @ 12:00 PM - 12:30 PM EDT
Join Evan Johnson as he speaks with security professionals about recent security news!
English
Security
News
Transcript (Beta)
Good morning and welcome to Hacker Time. I'm your host Evan Johnson from the Cloudflare security team and today is the penultimate day of this program we've been working on together over the last month or so.
There's been a few distractions where I wasn't able to make it some Fridays and then we had some security week content mixed in there and so our program hasn't really gotten finished but today I think is the day we're going to finish it and it should be really exciting.
So this episode we'll finish our code, we'll work on our code until it works, until we have the full login flow.
We'll talk about it for a few minutes, go over some areas of improvement that we might be able to focus on if we wanted to make our program better.
And depending on how much time we have we might end with some some chess puzzles or a blitz game or something and it should be pretty exciting and so I hope we wrap this up today and then for the next few weeks I'll probably be off air as Hacker Time headquarters relocates from San Francisco to Austin, Texas so that should be really really exciting.
I look forward to picking this up when I'm back in, well, one place since I'll be traveling and moving around for a couple weeks.
And without further ado let's get started. So we have our program and we've got our sign up page.
Everything is right where we left it but if you need a refresher we've been working on this sign up flow and it is coming along quite well.
It's a little bit wonky though because we're building it really quickly.
We're not worried too much about creating a little technical debt in the process but we want it to still be secure, we want it to work well, and we're building it on Cloudflare Workers and we've made a lot of progress really fast because sometimes these sign-in flows can be really complicated but we are ready to go and finish this.
So we're in just a few things left to do. We have to actually set the session cookie.
So we're creating a session in the back end, we're storing it once the user logs in.
However, we're not setting the cookie so we have no way to identify the user when they're logging in or when they return to the website.
So they press sign up, they type in their username, they type in a password, then they go to log in and they aren't actually logged in even if they have the right password.
We know when their password's right but no cookie to identify them when they return for subsequent requests.
So that's I think the big thing that we have left.
That's the very last thing to both put into the home page and the login page and the sign up flow support for this cookie.
And then we'll go over this. So let's actually get working on this cookie.
Okay, so sign up. We've got two post endpoints.
One is the create account one and one is the login one. Let's start with create account.
So after you, oh design decision. We're not going to do this on create account.
We're not going to set a cookie on create account. We only want to do it for login.
So you're going to go, you're going to sign up, then you're going to go to the login page.
And so in the login page, we actually need to set the cookie and then the login page should change based on whether or not you're logged in or not.
It should say welcome Evan. So here is us trying to set the header.
We have, we're putting the session. And then we're creating a new empty response here after a successful login.
We have a random string set as the cookie.
We're generating, let's rewind here.
So once we know that you've logged in successfully with the right password, we should do a few things.
We should generate a random cryptographically random secure string to be your unique identifier, your cookie.
We need to store a mapping, a key value mapping of the secure string as the key and then the value some object with your human readable identifier.
So your username or whatever you're logging in with.
And then we need to store that and then set a cookie with that key value, that unique identifier.
We need to set a cookie with that identifier as the value of the cookie.
That's what we need to do on login.
So we're doing the first three things. We are generating the random cookie.
We're storing the object in our key value pair. And then we are trying to set a cookie here.
It's not quite right. Let's try it. Log in with eeeeeeeeee, very secure.
Please don't hack my password. And let's see if the header gets set to right now.
We should see a response with a cookie.
It's very small. I'm sorry. Oh, it actually is set right here.
Here we go. I don't know if you can see this, but it is setting a cookie not properly.
But it's all mangled. That's funny. Set cookie ejcx and then our random identifier.
Not bad. What it should say probably is ejcx equals.
So set cookie syntax. Let's Google it. So the syntax of the set cookie header, it looks like set cookie colon.
And then you have the cookie name and then equals the cookie value.
And then you can have other metadata here with after a semicolon.
So cookie name equals expires, whatever, whatever, whatever.
And so that's pretty straightforward. I think we only need an equal sign here.
equals.
Is this going to work? That's too good to be true. I'm sure that's too good to be true.
Wrangler publish. And it's coming along here.
Let's be ready to log in.
This can take a little while.
All right.
This one I'm sure probably worked.
Look at that. I think it worked. We have a blank screen.
Wow, this is really small.
Tough to see. Response headers, all the SVC, CFA, CF request ID.
And then I think next is set cookie. Oh, no. I might have done it too quickly.
Cookie name. It should have an equal sign right here.
Let's try it one more time. And let's check our edit this cookie.
I haven't been able to figure out how to.
There we go. So, yeah, you can see in edit this cookie, this whole cookie is mangled.
It doesn't have a name.
OK, let's try it one more time. Edit this cookie is an easier way to debug this.
And it worked. OK, so we have an EJCX header here, which is our session cookie.
And then a random string. So the set cookie part of this worked. What we need to do is make sure that when we're sending this response, we're actually sending a redirect here on a successful login.
So users aren't looking at this blank screen.
So let us do a redirect to the homepage. Well, it's a little more complicated than just the other redirects.
And the reason is because I'm creating a response here, adding the header.
And now, as part of that response I've already created, I need to change it to be a 301 and set the location header.
So Cloudflare Workers redirect response. OK, so the response object should have a dot, some attribute to mess with.
And I can never remember it.
So let's see. This is not quite what I want.
OK, we want information on just the response object.
And we want to change.
We could change header to be to add a location header, which the browser will then follow to a new location.
That's one way to do this. I don't think there's a dot redirect.
Or that I am sure it'll do exactly what I want it to do.
So I'm going to do an r .headers.set, just like we did with the cookie location.
And then we just have to issue a URL, ejcx .net.
And this should probably work. That's an f.
And let's try it.
I think this will work. Oh boy, what have I done?
Yes, that is not proper JavaScript. You cannot separate this with a colon.
OK, while that's working, I'm pretty sure that's going to work.
We want to change the home page to recognize who you are. And we want it to render your name, hello, Evan, server side.
So that would be app.git on the home page right here.
Right now, it's just kind of blindly returning the login page. But we can actually add just a little logic to say, hey, if you're not logged in, return the login page.
If you are logged in, return like a welcome message, a home page or something.
So let's see how this went. Worker threw exception.
How is that possible? My code should be perfect. Headers.set.
I'm not sure what broke here.
Because it should be responding with the error if it catches in it.
Since the changes we made are in a try catch block.
That is very peculiar. Worker threw exception.
What did I do? I'm just going to validate that there's nothing.
OK, the location header should be working as I expect.
But I didn't change the status code.
I wonder if it's an issue with the status code. Let me just update the status code of the response object by changing r.status.
I think this will work.
I'll do status 301.
And I think this should work.
I think that maybe the workers runtime is unhappy with me that I'm setting a location header.
But also, and doing a redirect, but the status code is 200. Status codes are important.
Can't neglect those. That's my only guess here.
We'll see if I'm right, though. Wow.
If there's a bug here, like an obvious syntax issue, I'm not seeing it.
Because this is the same thing.
This should all be working.
This line, I didn't really change. It was working. You know what?
I might have a better plan.
Maybe we can avoid this whole thing. How could we do that?
We're setting a cookie. We could just render the home page here.
We could do that. That's one way. I kind of like the redirects, though, the redirect to the home page.
The thing that I'm really stumped on is why it's telling me worker through exception and not responding with my actual exception here.
Because all of this code is in a try-catch block. That's what's really stumping me.
So the try-catch is here. Let me just... There's a little weirdness with my spacing here, which is now sorted.
This looks correct to me.
Oh, duh.
I'm not returning the... This was an obvious issue. Okay. Doesn't matter how long you program.
You always have that where this issue is staring you right in the face.
You have to return the response object at the end of the function. I was not doing that.
So the worker was throwing an exception that could not be caught here because the function was ending without receiving a response object.
So the issue was actually outside of my...
The exception was being thrown outside of my code.
All right. This will work. I'm pretty sure this will work perfectly. If it doesn't work the first time, I just have to refresh and it'll work.
Okay. We're logged in.
Every time we do that, perfect. It just redirects us back to the homepage.
Okay. Let's fly through this part where we are changing the homepage. We need to call this get cookie thing.
We need to call off. We just pass it the request and we'll return a session.
Apparently we'll return a session. I haven't used this code in a little bit.
We might have some debugging to do, but let's assume it works for now.
And get app dot get. And the first thing we want to do is call...
What's the function? We just call off with the request and it will do all the cookie stuff.
So off request. Okay. Let's go back.
Off request. No await or anything. That's a little odd. Try. Let's put this in a try catch in case we have an issue.
Return new response E. Session.
If session.
If not session.
Actually, I think the right thing to do is if we have a session, render the shorter page.
Otherwise, we'll just continue here. Let's try to do return new response logged in.
Plus JSON stringify session. I don't know if we actually need to stringify this.
I think my code is stringifying it and off thing.
And then. Otherwise, it will return all of this stuff.
So let's try it. Nope.
Needed a extra curly brace there.
And what we should see here, I think we should see logged in.
If everything works perfectly, no debugging.
Which almost never happens, but it does happen. Sometimes.
We can actually just reload the page.
We don't even have to submit any credentials or anything.
Here we go. Logged in. That looks suspicious. I don't have any information here.
I do have this session. What is session? I wonder if it's a stringification issue or I'm double stringifying something.
That could be it. But let's check while this is loading.
Oh, boy. Get session. Does it return? Does it unstringify everything?
No.
It parses it, but that means I should be getting an object back, which means my stringification is probably the right thing to do.
But let me test my work by opening an incognito tab and going to the same homepage.
And it looks like it's properly handling the cookie. So if it finds the cookie, it's rendering this.
If it doesn't find the cookie, it's rendering this. This looks right.
So at least the logged in versus not logged in part looks correct. Ah, there's a promise somewhere.
OK, so I think this is what happens when you write code and don't test it.
So these are all async functions because I'm reading and writing from the session store.
Especially this one.
Yeah, this looks better.
But last thing, I want to actually see the contents of the session.
And this might actually render logged in with the object containing your username.
There is a 50-50 chance. It's always 50-50.
All right.
Is it going to work? I'm so nervous. It does.
It worked. OK, so we successfully have done it. We have gone end-to-end with where we built a login flow end-to-end, creating accounts, logging in, session management as well, all at the edge, all with Cloudflare Workers.
You can see my username here.
I could make it much better. Or if I do welcome, maybe I'll do this just for show.
Welcome session .username.
And then you've got to have an exclamation point.
Of course. The website is very excited.
I'm back. And I would say that wasn't super difficult. I really, I've never built one of those with Cloudflare workers before.
I've built a bunch with PHP, with Go.
It's usually building a whole login flow beginning to end is a great way to learn a new programming language or a new environment or a new framework or whatever it is you're working with.
I've never done it with Cloudflare workers.
And I would say that it was pretty easy because not even with my Cloudflare employee hat on, what I really appreciated was you could hack on stuff really fast.
This is not the nicest code ever. I should add tests for getting cookies.
And there's a lot of areas I can improve on. But just getting something working, an MVP, it was a really nice experience just being able to hack on something, see it work, run publish, see it live, and iterate really quickly.
That's the magic that I kind of experienced back working with PHP when I first got into web development where PHP has its whole own set of problems.
But the kind of magic of PHP is just being able to sit there in a terminal and be able to make changes really quickly, iterate, and improve whatever it is you're building.
And Cloudflare Workers kind of has that factor here where you can just make tons of progress really quickly.
So that is good. I feel like I learned quite a bit of gotchas here.
Debugging with the catches is a nice trick where it's kind of important if you're going to throw an exception, you need the error message.
And so it's pretty easy to just get the information you need by putting your code in a catch and responding with the actual exception message.
So that was nice. OK, so wrapping up this work, what went well?
What could we improve? I would say before this hits production, we'd want to double check this bcrypt library.
It's a little not super trusting of just any NPM library we find, especially if you are going to be hashing passwords potentially with it.
So definitely a review of this bcrypt library if you're looking for code, if you're looking to build a similar thing and use it in production.
I think I would write tests for this git cookie library. I would write this auth thing is pretty simple.
Random bytes is pretty simple. I'd probably refactor this a little bit where I put a lot of the things that are interacting with storage into its own JavaScript library or JavaScript file and a lot of the session stuff as well.
I think a lot of this could be cleaned up.
This is kind of like how I write Go. A lot of times we're handling the request is in the same path as the business logic, and I don't think that's the best way to do it.
It works pretty well, but you hit scaling issues in a larger code base with that.
But overall, I think the security of this is pretty OK.
There's some things missing, some session features missing, like we need logout.
We didn't implement logout. So that's an obvious missing feature that's kind of needed for a real serious login flow.
But overall, this was a great experience.
I will make sure this is on GitHub at github.com slash ejcxcftv. And I really appreciate you joining me for the last few weeks working on this.
Adios.