Web Middleware and CSRF0:44
5.3. All right, so with that done, let's cd in there, and open this in Sublime. Now here's what I want to do first. Let's go to our Kernel, so HttpKernel. And as you may or may not know, there is a web middleware group. So basically, anytime you apply the web middleware group to a set of routes, it will automatically activate essentially all of these middleware. And one specifically is called VerifyCsrfToken. Okay, but before we take a look at that, if you go to your routes file, I'm sorry, in 5.3 your routes file is at the top level, so you want a web.php. But yeah, you don't see any reference to a web middleware group, right? Well, this is applied behind the scenes in your RouteServiceProvider. So mostly you don't need to know about this, but if you want to figure out how these pieces work together, yeah, go to your RouteServiceProvider, and when we map our web routes, you can see that we require web.php, but we also wrap it within a
VerifyCsrfToken Internals1:34
if you want to figure out how these pieces work together, yeah, go to your RouteServiceProvider, and when we map our web routes, you can see that we require web.php, but we also wrap it within a route group where we set the middleware to web. Okay, so that's a long way of saying any route you declare within this file will receive all of these middleware items. Okay, so now we can take a look at VerifyCsrfToken. And if we go to it within our app folder, it's very simple because most of the work is being done in the parent class. So here, your $except property, this is where you could define any URIs that should be excluded from verification. And this will often be the case for things like webhooks, like a Stripe webhook or something like that. Now you might want to add an exception there. Anyways, if we go on up to the parent, and we go to the handle method, okay, here's what we want. So this check is just a long form way of saying, are we good to
might want to add an exception there. Anyways, if we go on up to the parent, and we go to the handle method, okay, here's what we want. So this check is just a long form way of saying, are we good to go? So are we reading? Do we basically have a GET request? Are we running in tests? If so, we're not worried about TokenMismatchExceptions at all. Have we declared anything like I just talked about within this except property that should allow us through? So we check that. And then finally, if the tokens match, then add the cookie to the response, and we're good to go. But otherwise, if that fails, then we're going to throw a TokenMismatchException. Okay, so how do we verify that a token matches what presumably we have stored in the session? Well, if we take a look at that, don't let this confuse you. In fact, why don't we just delete all of this to make it a little easier? Okay, so we grab some kind of predefined token from the
That means the user submitted a form that does not have the CSRF field. It wasn't included there because on this other malicious website that's trying to take advantage of you, often it's a form thread or something like that. Anyways, there, they don't know what the token is. So the user submits the form, it does go to our server. However, this is null, essentially. And does null equal what we have in the session? No. So something fishy is going on. So we throw an exception. That's how this works. Okay, but now if you were doing this manually, how do you set the token? And the answer is, it can be something as simple as str_random, like give me a 40 character random string, and then put that to the session. So it could be like a CSRF token. You know, you could do something like that to put this item into the session. Then within your form, you would add a call to reference this exact string value.
Session Token Generation4:43
like a CSRF token. You know, you could do something like that to put this item into the session. Then within your form, you would add a call to reference this exact string value. And that way, once the form submits, both of them should match up. And in fact, as it turns out, this is basically exactly what it does. Let me show you. Let's go to our session store. And here's where we start up the session. Oh, and real quick, just so you know, at what point does this get called? Well, once again, if we go to our kernel, come down, start session. That's part of the middleware group. And if we take a look at that, all right, so let's see what happens in the handle method. Okay, start up the session. So if we go in there, here we go. So we get the session object, and we start it up. Okay, so now what happens when we start? Well, we load things, but here's what we are interested in. If we don't have a token in the session, then create one.
session object, and we start it up. Okay, so now what happens when we start? Well, we load things, but here's what we are interested in. If we don't have a token in the session, then create one. So that basically equates to, once again, session, token, and then I'm not sure how long it is, but it's some kind of random string here. We put that into the session on every request if it doesn't already exist in the session. Okay, so let's see real quick. regenerateToken, and we are put into the session, and actually it is a random string of 40 characters. Okay, so I want to show you one more thing, though. Notice right here, _token? Well, in our forms, we talked about how you can say csrfField, right? And once again, magically, that just lines everything up. Well, why don't we see exactly what csrfField does? So this will be stored within your helpers file, Illuminate\Foundation\Helpers, and csrfField will create an input. It sets the
everything up. Well, why don't we see exactly what csrf_field does? So this will be stored within your helpers file, Illuminate\Foundation\Helpers, and csrf_field will create an input. It sets the name, and then the value is called a CSRF token, and now take a look right here. Resolve the session, and then get the token, and if we come back here and see what getToken actually does, it returns the token. So this makes perfect sense, right? When our request starts, if we don't have anything in the session called _token, we are going to create one and make it equal to a random set of 40 characters. Then in your form, you're going to make a call to csrf_field, and all that's going to do is echo out a hidden input where the value is equal to that random 40 character string. So then, going back, I know we're doing a lot of code here, but I think it's helpful to understand exactly what happens. Anyways, then when you submit the form, we verify
Building a Demo Form7:10
character string. So then, going back, I know we're doing a lot of code here, but I think it's helpful to understand exactly what happens. Anyways, then when you submit the form, we verify the CSRF token, we check to see if the tokens match, and we do that by figuring out, well, what is our 40 character string that we have in the session? And then we check to see, well, give me that hidden input called _token, and those two need to match up. If they do, everything's great. The request was made on our server. But if they don't match up, that means the submitting form is doing something kind of funky, something we don't like. So we're just going to get out of here and throw an exception. Okay, so I know that was kind of code heavy. Why don't we break it down, and I'm going to show you another very simple example. So welcome view, that's fine. Let's go in here, and let's clean some of this up. Let's get rid of this entire
Why don't we break it down, and I'm going to show you another very simple example. So welcome view, that's fine. Let's go in here, and let's clean some of this up. Let's get rid of this entire style tag. And in fact, let's get rid of all of that. Okay, so we're going to have a form, right, and we're going to set the method to post, and the action will be, I have no idea, submit. Or you know what, why don't we use a laracasts concept, maybe watching, and then we'll give it the ID of whatever it is you're watching. So like with laracasts, we can register that the authenticated user is currently watching the video with an ID of one, maybe something like that. Now we can set the URI like that, or of course we could do it as a hidden field. So type is hidden, the name will be video_id, and then the value once again will be like, you know, the ID of some kind of model. In this case, I'll hard code it. Anything is fine there.
the name will be video ID, and then the value once again will be like, you know, the ID of some kind of model. In this case, I'll hard code it. Anything is fine there. So now let's just say we're explicit about recording this. So we'll say record that you are watching this video. Kind of a silly example, but I think this will do just fine. All right, so if we go to myapp.dev, we see our form, and when we submit it, it's going to hit a watching endpoint. So Route::post('watching'). And how do we want to do this? We're going to have to set up a table here. Why don't we say the authenticated user that we don't even have, why don't we do this? We're going to cheat a little bit. We're going to say Auth::loginUsingId(1). So just log in a user with an ID of 1. Then we'll say $authUser is watching maybe the current video. We'll use implicit binding here. Maybe something like that. Okay, let's set up a
ID 1. So just log in a User with an ID of 1. Then we'll say auth user is watching maybe the current video. We'll use implicit binding here. Maybe something like that. Okay, let's set up a migration really quick. I'm going to do like I often do, an SQLite database, and then I'll say touch our database. And then php artisan make:migration for this videos table. Oh, and in this case, it's clocking because we haven't set up a users table. All right, run it again. That's set up our videos table. We can go here. I'm going very fast because it's not totally relevant to the content. And we'll just say the title of the video and that's it. Maybe the source, but it doesn't matter once again. Okay, so if I run php artisan migrate, we created our users table, we created our videos table. I need to create a model for Video as well, don't I? But yeah, you get the basic idea. So let's whip up a quick, I'm sorry, a quick User.
we created our users table, we created our videos table. I need to create a model for Video as well, don't I? But yeah, you get the basic idea. So let's whip up a quick, I'm sorry, a quick User. They have an ID of 1. So that's the person that we're going to sign in. And we're going to have them like the Video with an ID of 1 as well. So we need one for that as well. We'll have to do this manually new App\Video. And the videos title will be learn CSRF. And we'll just save it. Okay, so App\Video. All we should have one record in the database. And we're going to have this User record that they are watching this Video. So that means we need one more table, make:migration. And we could either call this we could we could use the convention of user_watching table. Or sometimes I don't honor that just because I prefer the table name to be a little more specific and explicit about what it is. Okay, so this is going to be
convention of User watching table. Or sometimes I don't honor that just because I prefer the table name to be a little more specific and explicit about what it is. Okay, so this is going to be our pivot table. And real quick, we'll just say an integer for the user_id. And then an integer for the video_id, that will be an index as well. We could set the primary key to both. This is fine, actually, php artisan migrate. Okay, so few minutes of boilerplate there just to set up a users table, a videos table, a Video model, and then a watching pivot table. So now if we wanted to say User is watching the given Video, let's set that up. And our User model right down here is watching the given Video. And our relationship. Well, actually, one thing I like to evangelize a little bit is that you don't always have to delegate to Laravel's relationship methods. So like I said, sometimes it's just easier to do stuff like this. So DB table, and then just insert
evangelize a little bit is that you don't always have to delegate to Laravel's relationship methods. So like I said, sometimes it's just easier to do stuff like this. So DB table, and then just insert a new record. I mean, I think in many ways, that's by far the most readable. But if you do want to use the relationships, you could say like, watching or that doesn't sound good watches, maybe. And that would be what's that going to be belongsToMany relationship. So User has many videos, or watches many videos. And if we click through there, we do need to be explicit about what that pivot table is. So we'll call it watching. Okay, so then we could say this watches attach video ID, and that will add the video ID and associate it with the User. Or I think I showed this in another lesson, you'd also do sync video ID, and then say false. And that would make sure that you don't accidentally attach the same video for the User multiple times. Like if you're not protecting
lesson, you'd also do sync videoId, and then say false. And that would make sure that you don't accidentally attach the same video for the User multiple times. Like if you're not protecting against that at the database level, with some kind of double primary key. And that basically protects you against adding the same videoId for a User multiple times. Like if you haven't protected against this at the database level with a maybe a composite key. Yeah, this can be kind of a helpful little trick there. But this is fine for our demo. So does this all make sense? When we post to watching/videoId, behind the scenes, we're going to fetch the video. And we're going to record that the User is watching the given video. That method will simply delegate to our relationship and attach a new record in the pivot table that has the User ID for this current instance, and then the videoId for the video. When we're done, we have nowhere to
delegate to our relationship and attach a new record in the pivot table that has the user ID for this current instance, and then the video ID for the video. When we're done, we have nowhere to go. So I'm just going to go back. So why don't we try this out? I'm going to go php artisan tinker. And as you know, right now, the watching table has nothing in it. But if we come back to Chrome, and we submit it, we get a not found exception. So maybe I recorded this wrong. So we're looking for watching/video, but we sent through Yeah, we didn't do that in this way. So yeah, you can do this in so many different ways. I accidentally did both. But either option will do the trick. So why don't we do it in this way? post to watching/1, we might want to I think it's okay for now. But maybe we could find a better user or id for that. Anyways, if we go back, we're going to try it again. And we do get a token mismatch exception.
I think it's okay for now. But maybe we could find a better user or I for that. Anyways, if we go back, we're going to try it again. And we do get a TokenMismatchException. So what exactly does this tell you? Well, it tells us that out of the box Laravel is helping us, it's protecting us. So it's not leaving it on your shoulders to remember to opt into this, we're going to give it to you for free. However, you do need to create that CSRF field in order to make everything work, right. So here, we would create a CSRF field. And if we come back and give it another shot, if I view the source, here's your input. And notice it does have that _token name. And the value once again matches up with what Laravel's Session class created right when the page loaded. So those two should match perfectly. Which means if I record this, we get Class video does not exist. Yeah, you saw that.
when the page loaded. So those two should match perfectly. Which means if I record this, we get Class video does not exist. Yeah, you saw that. If we record this, it redirected back. However, if we if we take a look at this, sure enough, we did record a new record in the pivot table. Oh, and for this little tip, a lot of people run into this one where you want to update the pivot table, but it's not adding the timestamps in the process. Here's what you do for that. On your User model, you want to make sure that you explicitly say with timestamps. So now if we say truncate that, just empty it out for us. Okay. And if we try to do it again, and fetch them, now the timestamps will be set. So that's a good little technique to know. Now though, imagine if we were to, let's see, if we go back to our web Kernel, or the HTTP Kernel, imagine that verifyCsrfToken
Simulating a CSRF Attack16:43
will be set. So that's a good little technique to know. Now though, imagine if we were to, let's see, if we go back to our web.php file, or the HttpKernel, imagine that verifyCsrfToken didn't exist, like maybe this is a makeshift project, and you didn't have any functionality like that. Well, even if in your welcome view, you didn't have a CSRF field, that means practically anyone, real quick, let's truncate this. Anyways, that means practically anyone could submit this form and create a record, even somebody on a different site altogether. Let's simulate that. We could use curl, we could actually new up a whole Laravel application and create a form for that. Actually, you know what, why don't we do that just because it's more fun, it's more visual, and I think it may help. So we'll say bad_app. Okay, so if we go in there, open this one up, we're going to say, within this files, web.php, let's clear out everything.
and I think it may help. So we'll say bad app. Okay, so if we go in there, open this one up, we're going to say, within this files, web.php, let's clear out everything. This is totally a valid website, and then it's going to have a form that will post, not to itself, but it's going to post to your website. So it's going to post to, what will it be, myapp.dev, /watching/1. Okay, so, or let's do another one, or let's do another one, like 500, or something like that. So, yeah, something funny is going on here. It's showing the User a form, they think they're submitting it to this totally valid website servers, but no, behind the scenes, they're actually pointing it to your servers, to the good guy's servers. So we'll say something like this, button, submit this form to earn $100. Okay, and we'll set the type to submit. Okay, so let's go to bad app, I'm using Laravel Valet,
to the good guy's servers. So we'll say something like this, button, submit this form to earn $100. Okay, and we'll set the type to submit. Okay, so let's go to bad app, I'm using Laravel Valet, so we automatically have a domain set up. Okay, so before we submit this, let's go over to the good app, php artisan tinker, and we will fetch our watching. Let's truncate that out, so we can start from scratch. And now, even on this domain we don't even know about, because our nice app didn't protect itself against CSRF attacks and forgeries like that, that means even though this failed, kind of a side effect from the route model binding, but even though it failed, it did submit to our server, not theirs. So if they were to tweak this to be an actual video that exists, in fact, why don't we do one real quick, factory, we can't do that. Video $video = new Video(); $video->title = 'another good lesson'; $video-save(); Okay, so now the
Wrap-Up and Best Practice21:00
As long as you know that for any form you're posting or patching, you want to make sure you add a CSRF field. As long as that's the only thing you know, you're good and you're safe. But yeah, if you wanted to know more about how this all works, how the pieces fit together, then I hope this lesson will have been useful to you. So let's bring back our CSRF token. We set the field. So now, this time, when this bad app tries to do something, yeah, not going to work at all. The tokens don't match, so something fishy is going on. So Laravel is throwing an exception and getting the hell out of there. That's how this all works. Okay, so thank you so much to Caleb Cruz for the question. If you have a question yourself, you can go on Facebook or email or Twitter and just make sure that you hashtag HelpMeLaracast so that I can see it, and I just might answer it in video form. I'll see you later.
