Should I write this weird code or is there a Laravel feature I can use instead?
Joel Clermont (00:00):
Welcome to No Compromises, a peek into the mind of two old web devs who have seen some things. This is Joel.
Aaron Saray (00:07):
And this is Aaron.
Joel Clermont (00:15):
We recently had a coded discussion that I thought might be interesting to bring on to the podcast. It had to do with, I guess maybe kind of a weird scenario but I'll talk through it, get everybody up to speed as to what the technical thing I was trying to do involved and then the solution that we ultimately landed on. I think there's kind of a neat underlying principle that came from it. So I'll start by setting it up. So, what was I trying to do? Well, I was writing a controller. I guess not really a controller, but it was receiving a webhook in the Laravel app and it was a webhook from Twilio. One of the things you're supposed to do as a good programmer is verify that they sent it, right? There's this whole verify signature method that they give you being their client. But in the docs, they called out, "Hey, we also include the body of the message in the verification. And Laravel and other web frameworks will automatically trim strings off of user input and so don't do that because your signature will fail."
Aaron Saray (01:20):
So the point is that in order to make the signature to check the validity of the webhook, you have to take the body message and other things and make a signature and then compare it? Okay.
Joel Clermont (01:32):
Correct. Right. So if the payload was altered by Laravel as part of the trim strings middleware... Which is a great thing, there's a reason it's in the global middleware and it's a very sane default to have on your projects. But in this one particular instance it could potentially have broken things. Now, again, I know, Aaron, you and I started out by arguing like, "Well, why would we even let somebody send a message with leading and trailing strings?" And like, "Well, we don't control the client." There was another way to maybe tackle this but I ultimately decided, "No, we have to solve it purely on the server side." Which means, how do we disable the trim strings middleware just for this one route of this one webhook handler? Is that a fair setup?
Aaron Saray (02:16):
Yeah. I how you made that discussion sound so pleasant too. Like, "Why would anyone do this?"
Joel Clermont (02:21):
Yeah. It was a valid question too, Aaron. But I think it was even a Saturday night or something, I'm like, "I just have to fix this. We got to get this done." Here's where I went first and this is maybe... I'll just say I'm every man developer and maybe I'm not alone in reaching for a solution which would work. It would work, I don't think you disputed that, Aaron, but probably wasn't the right way to go. First of all, just to point something out. There is ways in the routes to remove middleware for a specific route but that does not work with global middleware. It only works with route-based middleware groups. Anyways I said, "Well, I guess we could just move trim strings out of global middleware and make it a route based middleware, right? It wouldn't be that hard, you could just delete it from that one line with the kernel, or wherever that is where we set up the routes. And then in the routes file, you just wrap everything you have and put it back. And now I can do without middleware on it with just that one call." So, Aaron, why did you not that solution that I came up with?
Aaron Saray (03:29):
I'm already rubbing my face right now. Well, at the time... And it's great to look back at this because we sugarcoat and bring this down a little bit. But at the time I was, "Joel, just stop lighting everything on fire." I was like, "This is such-
Joel Clermont (03:44):
I remember that expression.
Aaron Saray (03:45):
"... this is such an explosive solution to what is, I think a relatively simple problem." I didn't really have much of a concrete example of why not to as much as it really seemed, for this one piece of code in one place only, we now have to change a core functionality of how our project is set for everywhere else. I was like, "I just don't know if that's the right thing. It just feels wrong." I don't think there's anything wrong about what you said. Like, we could have done that. Moved it from global middleware or whatever, but I just didn't the idea of now I had to kind of think about now I have to manage another piece of middleware when every other project I have, every other single one, it's the global middleware applied and I never think about it again.
Joel Clermont (04:30):
Yeah. It's not just the default for our projects but it's the default for Laravel. The more I thought about it and after I disputed you. Like, "I'm not burning the world down or lighting it on fire." But who knows what's going to change in the next version of Laravel? And there's assumptions baked into things like the Upgrade Guide that you likely haven't messed with the global Middleware. So you're right, it's one more thing to juggle now, but also something that could come back and bite you later when you're upgrading or installing a package that assumes it's there, or things that. There's all sorts of other reasons. This was probably too broad of a fix for a very targeted need.
Aaron Saray (05:07):
Yeah. So one of the things I had remembered was I think the verify CSRF cookie middleware had some sort of property in it that says, "Except for these routes, do this on everything." Inside kind of mentioned, "It'd be nice if that middleware had this," I don't know if it does. Or, the next best thing is I'm like, "Let's make our own middleware that is called this and put it in our own app and we'll make that the global middleware." And in there we'll just check for our one route that we know and we'll just get out of there early. Otherwise, we'll let the parent handle what it's going to handle.
Joel Clermont (05:43):
Yeah. When you said that, I'll be honest, my first reaction was, "Well, this also feels too broad of a solution. We're overriding a whole middleware just to do this one thing." And I was thinking of the future upgrade ramifications but then I looked at it and I noticed, Trim Strings is a unique one of these global middleware because it puts a file in your app directory that allows you to override things like specific fields. For example, it will not trim strings on a password or password verify field for obvious reasons. You want those to be exactly what the person typed in. I got excited, I'm like, "Oh, so I don't have to override the middleware, I'll just use this file here. It's already like an extension point given to us." But I quickly realized there wasn't a way in that file to do it for a particular route. Then I kind of went back to the drawing board.
Aaron Saray (06:32):
Right. If you would've put body in there or something, like for body of message, it would've not trimmed all the bodies.
Joel Clermont (06:38):
Yeah, it would've been too broad. It wouldn't have been just that one route.
Aaron Saray (06:41):
Okay.
Joel Clermont (06:41):
Yeah, I thought about that. Because there was only one field in this API webhook payload that was user-generated content that could have had leading and trailing strings. So that could have been a way of doing that but I think it was a generic field message. Which was probably way too broad to not want to trim that everywhere else in our application. Anyways, I dug into the... Well, I want a step back. I looked at the docs, right? I'm like, "I can't be the only person that has wanted to do this," and apparently I am but there was nothing in the docs about this. So I just kind of dug into the trim strings middleware itself, just to... You know, sometimes there's things in there that can give you a clue, and I found something. I forget if it's handle or what it's called, but wherever it does the actual trimming, where it looks at the fields to skip there's also a thing where after it's done it looks at just another callback. I think it was called... I forget what the name of the property was.
But I could tell it was looking for something else, so then I'm like, "Well, where does that get set?" And I stumbled upon a static method called skipWhen, which is on the Trim Strings middleware and that's exactly what I needed. That ended up being the solution and then I made myself a note like, "Hey, I should submit a PR to put this in the docs for the next person, which might be me three years from now that's looking for this." But it ended up being a one...it was a one line change in the, I think it was the route service provider. I can't remember. It was either app service provider or route service provider.
Aaron Saray (08:09):
Really anywhere you could have put it.
Joel Clermont (08:10):
Yeah, you could have put it anywhere. Then one small function, which looked at the route and said, "Return true when it's this route." It worked and I wrote tests and everything passed and end of story. First of all, Aaron, what did you think about the final solution? You agree that was the right way to go?
Aaron Saray (08:27):
Yeah, it's better than the one I came up with. Because, I mean I didn't really having to override, like I said, one either but I just kind of reached there when I'm like, "That is close as possible to the original solution, extending a child class or whatever," than pulling it out and putting it somewhere else so I felt that was closer. But... I mean, your solution that you finally found there I think is obviously even better because that's what it's designed for, I'm sure. So I really like that, that's a good solution.
Joel Clermont (08:57):
As an aside, I went and looked when that method was added because sometimes that'll give you cool, "Well, why was this created?" And it was actually created, I think for Liveware and Octane. There was these packages that needed a way to disable the trims strings middleware. I don't know why but I'm sure it was a valid reason like mine and so it was kind of created for that. So maybe the thought was a normal user wouldn't do this, so it didn't make its way into the docks, or it could have just been an oversight. It was like 8.43. so it wasn't a major release of Laravel when it came out. It was something that was added much after the big major release. The principle, I said there's kind of an underlying principle here, and I think what I took away from this is to try to scope your solution properly. Don't make such a huge sweeping fix even if it addresses the problem. Try to make it more targeted to avoid unintended side effects. Then second is to know what the framework gives you in terms of extension points, so that you can leverage them and actually make a very targeted solution that the framework was designed for you to inject your own logic into places like this for these exact purposes.
Aaron Saray (10:11):
I wanted to add on two more principles.
Joel Clermont (10:14):
Oh, nice.
Aaron Saray (10:14):
I think that the third is that you have to pay attention. At least this is how it works for me and I've seen it happen for Joel too. You have to pay attention to kind of how you feel about the solution too. If the solution feels like, "Why would anyone ever..." and you're got to do something totally weird and you're just like, "I'm just going to do something totally strange," or if it feels overly clever, those are two signs that like maybe there's something different. I would hazard to say you probably felt one of those two with your first solution and then when you finally found the skipWhen it didn't feel clever, it just felt understood or deeper. Like, you have a deeper understanding of how you're solving it. So that's the next thing is kind of pay attention to how you feel about your solution because that'll usually be some sort of clue into whether this is the right thing to do or not.
Joel Clermont (11:06):
Yeah. Well, and as proof that I felt that way with my first solution, I brought it to you and I'm like, "Hey, what do you think?" Because already I kind of knew it wasn't great but I was hoping you had something better. So, you're right. I had that initial hesitation even though I came up with this solution. It's like, "Ah, this isn't quite right."
Aaron Saray (11:24):
Then the fourth thing to kind of pull out of this too as another example is, as a code reviewer or as a partner in a project it's perfectly great to ask like, "What do you think about this solution?" so that was a good step, Joel. Then I gave a solution. It doesn't mean that my solution was right and I think we found that here too. Which is to say that you're like, "Ah, so I had a solution, yours was maybe better, but I ended up coming back with my best solution." So not only as a reviewer we have to understand that sometimes when we give reviews, don't be upset or offended if the receiver takes your review and then makes it into something better. That's what they're supposed to do, it's all about the code. And then second of all, as the person who's getting the review don't always just blindly follow the solution of someone else. Because, Joel and I have worked together long enough now that he knew that my example of a solution wasn't necessarily the exact implementation either. I was like, "You could do something like this," but that didn't mean do that. It was more a shorthand to, "You see how my mind went to this solution? Put your mind there too and you'll come up with the right solution."
Joel Clermont (12:28):
It was like, "Definitely don't do the thing you want to do. Here's something else you might want to try."
Aaron Saray (12:34):
Right.
Joel Clermont (12:42):
Recently it was kind of end of the year financial accounting stuff I was doing. I was working with a guy, a really good accountant, and he wanted me to move some money and I said, "Okay. Where can I log on and do that?" And he's like, "Oh, you have to send a check." And as I'm writing out the check, I'm just thinking, "This is kind of a weird way to move money around." Just as I was writing out... You know, there's weird things you do on the check where you write out the number of cents, so maybe this is an even amount of a thousand dollars or something. But you have to write out the zero zero because, what? I don't know, the person I'm sending it to is got to be like, "Ha-ha, I'm got to add 50 cents to this." You know what I'm talking about?
Aaron Saray (13:28):
Right.
Joel Clermont (13:28):
When's the last time you wrote a check, Aaron? Is this totally foreign to you?
Aaron Saray (13:32):
Yeah. I'm like, I guess I put nil cents if I do zero. I don't do zero zero over a hundred, but-
Joel Clermont (13:40):
I guess they could add a one and make it a hundred cents and then they get an extra dollar out of you.
Aaron Saray (13:45):
Well, yeah, it is so weird. I mean, I guess the biggest security thing about that is it takes forever for it process. I mean, otherwise it's just a piece of paper with some numbers on it where you're like, "Trust me, these numbers I won over there."
Joel Clermont (14:00):
Right.
Aaron Saray (14:00):
This is all we really do online anyway too. We're like, "Click, click," and now money is different.
Joel Clermont (14:07):
Yeah. Well, I had to send out three of these and one of them I forgot to sign. So I got an email back from the guy and he is like, "Hey, you have to sign it." And I'm like, "Is there any way I can like..." Because there was a deadline approaching and I don't want to wait another three plus days for it to go through the mail. But that's what I ended up doing because like, no, there was no way to alter it once it went out. So it just seemed kind of goofy to me. And related to that, my dad has a friend who refuses to use any sort of credit cards or online payments and only pays with checks because he thinks it's more secure. And I'm like, in what universe is a piece of paper that's being put in a box and going through a bunch of other places and ending up on somebody's desk, how is that more secure than a credit card or an online payment? But not an argument that I wanted to have.
Aaron Saray (14:59):
Or, don't give me staring on people that, like when you're in a grocery store are like, "Yes, let's write out a check." Remember when cards went from swiping to them being a chip reader?
Joel Clermont (15:11):
Yep.
Aaron Saray (15:11):
And you're like, "Oh, it's got to take six more seconds." No, not when someone's writing a check. Or, especially the people that won't finish the check until they put it in their ledger as well in a checkbook.
Joel Clermont (15:23):
Oh, yeah.
Aaron Saray (15:23):
And they're like doing math. I'm like, "I'll pay, just please let me out of here."
Joel Clermont (15:29):
Please.
Aaron Saray (15:29):
Like, "What are you doing?" And then my favorite part then is they give them a check, it runs the machine and immediately withdraws, or whatever it does, and then they hand the check back to them and be like, "Here you go." What was the point? Just give them a card.
Joel Clermont (15:43):
Yeah. I'm with you on that.
Joel Clermont (15:49):
Do you have a Laravel SAS that maybe you need a little help looking at your architecture choices and moving to the next level?
Joel Clermont (15:55):
That's something we enjoy helping out with. If you head over to nocompromises.io, you can schedule a free 30-minute call and see how we can help.