تماشای این درس نیاز به اشتراک حرفه‌ای دارد.

Register reCAPTCHA keys0:00

Let's review the ins and outs of reCAPTCHA verification within a Laravel app. So I have a fresh install here. Next, visit google.com/reCAPTCHA, sign into your Google account, and visit the admin console. In here, we can register a new site. You can choose between v3 and v2. Believe it or not, v2 is actually more popular at the time of this recording, and that's because v3 is slightly a different approach. It returns a score based on how reliable the verification was, and that can sometimes be a little confusing. So we'll stick with v2, and I want invisible reCAPTCHA, rather than forcing the user to click, and I'm not a robot checkbox. Okay, next, the domains will be your production site. In our case, if I switch over to PHPStorm, you can see this is the directory name, and because I'm using Laravel Valet, I can say reCAPTCHA demo.test.

In our case, if I switch over to PHPStorm, you can see this is the directory name, and because I'm using Laravel Valet, I can say reCAPTCHA demo.test. All right, agreed to the services, and we're ready to go. So now I have a public key and a secret key, and I'm going to store those within an .env file. So right down here at the bottom, we'll say reCAPTCHA key, and then I'll duplicate that for the secret, and we'll grab that secret and paste it in there. Okay, next, we're going to reference that from a config file, and usually your services file is a good place for this. At the very bottom, we'll say reCAPTCHA, and we're going to store the key as well as the secret. All right, so here we'll say reCAPTCHA key, and then grab the reCAPTCHA secret. So now, of course, anywhere in your Laravel app, you could say config('services.reCAPTCHA.key') to grab your public key.

Add client-side integration1:36

All right, so here we'll say reCAPTCHA key, and then grab the reCAPTCHA secret. So now, of course, anywhere in your Laravel app, you could say config('services.reCAPTCHA.key') to grab your public key. Okay, great. So let's switch back. We'll go to settings, and actually, I think we're good there. So now if you check out the documentation for Invisible reCAPTCHA, and you have a couple choices here. So the easiest option is to automatically bind the challenge to a submit button. Here's how. First, you want to pull in the reCAPTCHA script, and then next on your form, basically the way it works is it will intercept the submission of the form, and based upon whether you seem suspicious or not, you'll see one of those pop-ups where it tells you to click all of the school buses or something like that. Anyways, once you confirm, it will trigger a callback that you provide where you can then proceed, and usually that means actually submitting the form. All right, let's give this a shot.

Anyways, once you confirm, it will trigger a callback that you provide where you can then proceed, and usually that means actually submitting the form. All right, let's give this a shot. So I will copy the script, switch back, and actually, because it's a new install of the framework, why don't we do this? I'm going to set up a layout file as a component. So we'll call it layout.blade.php. Here will be my default content. And yeah, maybe we'll work with this idea of posts. Like we always do, it's an easy thing to grasp. So maybe a form to create a post. So I can reference that layout.blade.php file we just created, and we'll say create a post, and then we do have to reference that reCAPTCHA. So you might want to do it conditionally based on the pages that require it. Just for now, though, I'm going to throw it in the layout.blade.php file right up here. Okay, next, if we switch back, don't forget we need a callback function.

Just for now, though, I'm going to throw it in the layout file right up here. Okay, next, if we switch back, don't forget we need a callback function. So we'll just go ahead and paste that in here. So you see how it gets kind of messy, right? Especially if you want reCAPTCHA on multiple pages. But again, for now, we'll declare it here, and then we'll say here, we can be pretty generic. Let's just say document.querySelector. Just find the form and submit it. But of course, be as specific as you need to. Okay, next, define your submit button, give it a class of g-reCAPTCHA, provide your site key, and then provide the callback with a name of onSubmit. All right, let's get started. I'll add our form here. That'll hit an endpoint of posts. And yeah, maybe we'll do a form input for the title. So I have a snippet here.

I'll add our form here. That'll hit an endpoint of posts. And yeah, maybe we'll do a form input for the title. So I have a snippet here. It's just a label with an input with a name of title, and then any error validation there. Okay, so you can see I'm referencing these tailwind classes. With that in mind, why don't we go ahead and pull that in? You don't need to know anything about it. It just makes the page a little bit nicer. Okay, let's go on back. So I have a title for the post. I'm going to do another one for the body of the post. Once again, we have a textarea and then error validation. And then finally, we're going to have a submit button to submit the form. All right, I'll let you take a look at that. It's pretty basic stuff.

Create form and routes4:45

And then finally, we're going to have a submit button to submit the form. All right, I'll let you take a look at that. It's pretty basic stuff. Okay, so now let's create an endpoint. And we'll just say, listen for a GET request to /post/create. That'll hit a view post on create. Next, add another one. We want to POST request to /posts. And all I'm going to do right now is dd all of the $request data so that we can observe it. All right, going a little bit fast here because most of this is Laravel 101 type stuff. So let's try it out. If we visit this endpoint, here we go. We need some padding there. So let's go to our layout file and I'll just throw it on the body. Give us a max width of how about small and then center or set the margin to auto.

We need some padding there. So let's go to our layout file and I'll just throw it on the body. Give us a max width of how about small and then center or set the margin to auto. Yeah, and then maybe push it from the top a little bit. Okay, finally, style the heading and then we are done, at least with that portion. So right up here, why don't we say how about text-lg font-bold and then a little margin-bottom. And that's all we're going to do here. Okay, so of course, if I fill this out and submit the form, when we dump the request data, this is what you'd expect. However, of course, the whole point of the lesson is we want to hook in reCAPTCHA verification. So don't forget in the documentation, give it a class, give it a site key and then give it a callback. All right, right down here, give it a class, give it a site key.

So don't forget in the documentation, give it a class, give it a site key and then give it a callback. All right, right down here, give it a class, give it a site key. I can read that as we learned services.reCAPTCHA.key. And then finally, a callback will again be the name of our callback here. All right. So now because we've provided this class and because we've imported the script, we should see a little reCAPTCHA badge. And there it is. Okay, so now if I submit the form, we do see reCAPTCHA response with this long string that we can use on the server side to perform the verification. This will ensure that the response is correct and ready to go. But notice that I didn't have to fill out that challenge.

Verify response server-side7:30

And basically that amounts to grabbing that value, which was the long string we just saw, and then you're going to hit an endpoint. You'll hit this endpoint, you'll pass through your secretKey, the response that was submitted to your server, and optionally the user's IP address. The result of that response will be JSON, where you can check the success, and if it's true, you're good to go. All right, so let's come back up. I'm going to copy this whole thing. And how do we perform this? I'll give you a little tip.

And how do we perform this? I'll give you a little tip. Often what people do, and for a small project, go right ahead. It's very easy. But often what people will do is they will start submitting their curl request directly within the route. And yeah, again, for simple projects, I'm all for it. Keep it simple. But generally I would recommend doing this as a validation rule because that's effectively what it is.

Build validation rule8:18

But generally I would recommend doing this as a validation rule because that's effectively what it is. We are validating that your CAPTCHA verification passed. All right, we can do that with php artisan make:rule, and I'll call it reCAPTCHA. So this will create an app/Rules directory. And again, if you're not familiar with the concept of rules, just think of it as custom validation. Here's the method we really care about. Check if our validation passed. Okay, well, we're going to make a curl request to that endpoint.

Check if our validation passed. Okay, well, we're going to make a curl request to that endpoint. Pass through the necessary data, and then check the result and return if it was successful. That's all we're doing here. So we can use the HTTP facade to make a post request to this endpoint. But real quick, it will expect that to be submitted as form data. So there's a helpful method here called asForm. Make that post request, and we will specify, what is it? The secret, the response, and the remoteIp.

Make that post request, and we will specify, what is it? The secret, the response, and the remote IP. Secret, response, and the remote IP. Okay, the secret is going to be config('services.reCAPTCHA.secret'). The response is going to be the value. So remember, the way this would work would be you're going to say request->validate('g-reCAPTCHA-response'). That is that key that Google adds for you. And I'm going to say that is required. But also, it's going to be using this new reCAPTCHA rule.

And I'm going to say that is required. But also, it's going to be using this new reCAPTCHA rule. So when we call this, it's going to automatically trigger the passes method. Attribute is going to be the key, and value is going to be the value associated with that key, or the value that was returned and submitted as part of that post request. Okay, so I'm going to send through the value there. And then finally, the remote IP is optional, but you can grab that easily with request()->ip(). Okay, next, check the results and return if it was successful.

but you can grab that easily with request ip. Okay, next, check the results and return if it was successful. So here's our response. I'm going to grab the JSON response from that request, and we're going to return whether or not the response, and let's come back and check. We're going to look at this success boolean. So if that returns true, then the validation passed, and we're good to go. And I think that'll do it.

and we're good to go. And I think that'll do it. So here I could say reCAPTCHA verification failed, and get rid of the constructor, reformat, and there is your custom validation rule. So now, you can reference it like so. I don't even have a Post model here, but yeah, you would say before you get to this point where you say Post::create, we're going to validate the request. And maybe we'll say, yeah, the title is required,

we're going to validate the request. And maybe we'll say, yeah, the title is required, the body is required, but also that g-reCAPTCHA response is not only required, but also we're going to use this new custom validation rule. So now if any of these fail, we won't get to the point where we create the post. Instead, Laravel will redirect back with the errors. I'm just going to simulate, though, because we don't have a post table or a model.

I'm just going to simulate, though, because we don't have a post table or a model. By doing a dump and die with validation passed, and we are ready to create the post. Okay, so let's give it a shot. We will create a post, give it some gibberish, and there we go. The reCAPTCHA verification is working. Okay, so it works,

The reCAPTCHA verification is working. Okay, so it works, but it's kind of annoying to me that there are so many steps. And I assure you, three months from now, when you need to do it again, you're going to forget these steps. And you'll keep having to go to a place where you implemented it. To say, okay, I guess I have to do that class, and then siteKey, and then onSubmit. I have no idea where that was declared in my code base.

Refactor into Blade component12:17

and then siteKey, and then on submit. I have no idea where that was declared in my code base. It's all dispersed. So instead, what you might consider doing is creating a component. So if you're using Vue, maybe you could have a reCAPTCHA component. Or on the other hand, if you're using something like Alpine, maybe you could mix that with a Blade component.

if you're using something like Alpine, maybe you could mix that with a Blade component to get the same effect. Why don't we review that approach? In my components directory, I'll add a new one here, and we'll call this, how about reCAPTCHA.blade.php? And we'll populate this with an empty div for now. So yeah, in situations where you don't want to automatically bind it, but you want to manually render and execute the validation,

You can specify these, or you can pass it through as a simple object when you call execute. And yeah, this greCAPTCHA, this is available after you pull in the script in your head tag. You can call greCAPTCHA.render. Let's see. Lots of stuff here. Just come along for the ride. You can call greCAPTCHA.render,

you could do that here, and it'll automatically trigger that function. Next, the parameter renders what we want. So it defaults to onload, which means it will automatically render the first widget that it finds or the first widget that matches that greCAPTCHA class we added. In our case, though, we're going to do it explicitly. So I can say render equals explicit. Next, though, we still have this thing where we're loading our reCAPTCHA here,

Next, though, we still have this thing where we're loading our reCAPTCHA here, we have a callback here, and then we also have a Blade file where we would have to add a bunch of classes. It's still kind of dispersed. So another option you might consider is something like this. If you're only going to have one on the page, you could set up a stack for your scripts,

If you're only going to have one on the page, you could set up a stack for your scripts, and then I could conditionally add it on when I need it. Something like this. Now, if you're not familiar with stacks, I'll show you how to use them. We could do it right up here. I can declare a stack called scripts, and now anywhere in my code base, if I want to push to this stack,

and now anywhere in my code base, if I want to push to this stack, I can just say push scripts. And now I could do this throughout my project as many times as I want, and all of those occurrences will be appended wherever we declare the stack. So have a look. If we come back to our form and view the source, I don't think we're going to see reCAPTCHA anywhere.

If we come back to our form and view the source, I don't think we're going to see reCAPTCHA anywhere. Nope, we have the old class there, but we're not importing the script. However, as soon as I reference our new Blade component, so I could do it here, x-reCAPTCHA, if I come back and give this a refresh, notice that it now imports it. Now, a little caveat here.

So yeah, if that's the case, you'll just want to load it only where required. Like maybe you could say push to scripts here, and that way you don't get the duplication. Okay. Next, I definitely don't want this onSubmit function here, so we're going to handle that a little bit differently. I'll get rid of it entirely. And then finally down here, we're no longer automatically binding it,

So this is where Alpine's going to come into play. Why don't we pull that in? Alpine. And then within our component here, I will declare this as an Alpine component. And we could, of course, declare all of the logic inline. But just to show you multiple ways, as you've already learned, we could reference a global function. So for example,

we could reference a global function. So for example, if I have a script here called window.recaptcha equals a function, as long as we return an object here, that's going to do the trick. So let's think about it. We need at some point to initialize our recaptcha, and that's where we would render the widget. We would also need some way to execute the challenge

and that's where we would render the widget. We would also need some way to execute the challenge or invoke the challenge if necessary. And then lastly, we need some kind of callback function. So after we've invoked the challenge, when it's complete and we're ready to submit the form, let's trigger this function here. And I think this is where we will dispatch an event announcing that recaptcha verification is complete. And we are ready to submit the form.

announcing that recaptcha verification is complete. And we are ready to submit the form. Something like that, right? Okay. So to start, I could say on X init, we will call this init method. And yeah, I could say alert init. And if I come back and give this a refresh, we get our alert. So it is loading.

And I bet, yeah, it's going to be inconsistent. But other times, it's not. So all of these cases, it's probably cached. But if I rerun it, ah, well, you did see the one time where it hadn't fully loaded yet. So we can get around that in a couple ways. We could set a callback here, but that ends up getting kind of weird how we can call methods within our Alpine component.

So next, I'm going to say gRecaptcha when it is ready to go, when it's fully loaded and we can reliably render the widget. I'll say gRecaptcha.render. It's going to render it based on this div. So I can say this.l. And then I'm going to pass through those values that were part of the HTML attributes before, like siteKey or dataSiteKey is what we had,

those values that were part of the HTML attributes before, like siteKey or dataSiteKey is what we had, dataSize and dataCallback. So our siteKey is going to be, once again, config('services.recaptcha.key'). The size is kind of a weird name, but that would be invisible. And then finally, the callback will be, what did we call it? onComplete, this.onComplete.

what did we call it? OnComplete, this.onComplete. But just be careful, we don't want it to rewrite the, or we don't want it to rebind this. So I'm going to force it to bind to the current instance. And that way, once this method is called, this is still going to refer to our Alpine component. Okay. So are we all set to go?

Okay. So are we all set to go? We're now going to do it manually. So we declare a div here. And once gRecaptcha is ready to go, we render the widget to that element, and we pass through the necessary data. So we're just doing it manually, whereas before, Google was doing it automatically.

whereas before, Google was doing it automatically once it could find the button with that class name we provided. Finally, when we are ready to execute it, invoke the challenge, I can say gRecaptcha.execute. But do note, at this point, we're still not calling execute. So keep that in mind.

we're still not calling execute. So keep that in mind. All right. So if I come back and give this a refresh, there we go. We are now rendering the Recaptcha. But we haven't yet executed it. So if we run it, yeah, you can see we're not showing the errors yet, but the validation redirects back.

yeah, you can see we're not showing the errors yet, but the validation redirects back. Why don't we show that really quick? Down here at the bottom. And actually, I have a little snippet here. Pretty common. In an unordered list, if we have any errors, iterate over them, and for each one,

iterate over them, and for each one, display the error as a list item. So now we fill it out. And yep, we still haven't executed the Recaptcha. So we have no response key to verify against. All right. Now how do we execute it? Because before, the Recaptcha script was automatically intercepting

And we can do that simply by declaring xdata. So now, for example, I could say, when you submit this form, well, prevent the default action, and say submitted. It's this easy to intercept the submission of the form. So we run it, and sure enough, you get the alert.

You might have something like that. And then somewhere in a script, you might say, in your view component script, you might say refs.recaptcha. And then on that view component, you might call a method execute on it. But in this case, we don't have it. So we're going to stick with simple events to communicate.

we don't have it. So we're going to stick with simple events to communicate. For example, when we submit the form, let's dispatch something like Recaptcha, verification, whatever you want. And let's go ahead and reformat like so. All right, makes sense? So dispatch is going to fire a custom event on the element called Recaptcha

So dispatch is going to fire a custom event on the element called Recaptcha that's going to bubble up all the way to the window. So now on our Recaptcha component, let's simply listen for it. And we can do it right here. Listen for a Recaptcha event, but not on this element here. I want to hear it on the window. So if anything bubbles up to the window,

We have our form. When you submit it, you hit that button, we intercept the submission and we instead dispatch a new custom event called Recaptcha. So that's going to bubble up all the way to the window. And on our Recaptcha Blade component, we listen for it.

And on our Recaptcha Blade component, we listen for it. And in response, we call execute where we alert, but then we do actually execute and invoke the challenge if necessary. So I think if we run this, we're probably still not going to see anything. However, there is a difference. Yeah.

a couple of different ways based on how coupled you want this component to be to the form. So for example, after we invoke the challenge, once the user has provided their selection, that's what this callback is for, where we call onComplete. So I can say alertComplete. It's a little confusing

So it's a little more coupled because we are assuming there is a parent form nearby. But yeah, that would be an option. Another option, as you see here, would be we're going to dispatch another event, signaling that the Recaptcha verification is now complete. Either one works based on your needs. But anyways, if we run it, there we go. Now we've achieved exactly what we had before,

there we go. Now we've achieved exactly what we had before, but this time we manually rendered it. So if you can and if you don't mind, I think this is a perfectly easy way to handle it. But yeah, if you wanted to take this approach, I think with view, I'm sorry, with Alpine, I don't know if there's an easy way to call dispatch because it's kind of a magic function. You might check me on this,

because it's kind of a magic function. You might check me on this, but I don't think I can do this. So, and there might be a way to pass it in. I think I've read stuff like this before where you pass it in. Honestly, I'm not entirely sure. So why don't we just do it the traditional way where I could say on the root element, let's dispatch a new CustomEvent

So this would be the event way to handle it. A little more convoluted, but you could then say, what are we listening for? Recapture complete on the window. And then here I could say, get the form. So this itself is an Alpine component. So I could say l.submit. Yeah, that would be maybe a little more decoupled, but also a little more confusing way to handle it.

Yeah, that would be maybe a little more decoupled, but also a little more confusing way to handle it. Let's give it a shot though. We run it and that works as well. Why don't we stick with the easy approach though? So I'm going to get rid of that and bring it back to what we had before. And I'll let you take a look at this. There's your Blade component. So now notice on our layout file,

There's your Blade component. So now notice on our layout file, we no longer have that confusing onSubmit global function that I promise you you're going to forget what it's for six months from now. So now think about it. Anytime you need this functionality, all you have to do is spit out your recaptcha component. That will take care of the initialization, rendering the widget,

That will take care of the initialization, rendering the widget, and also listening for a event called recapture. So on your form, at some point, when you're ready, dispatch that event. And again, in almost all cases, it will be when the form is submitted. So prevent the form submission, dispatch that event, recapture will catch that, it will execute it. And then once it's complete,

دوست دارید گاهی خبرهای Laracasts را ایمیل کنیم؟