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

Configuring Authenticator Options0:00

If you've been good and you've done your homework, you should now be starting to understand the different options that WebAuthn makes available. So if you set Authenticator attachment to Platform, you have to create the passkey on the platform. You can't use a USB security key or, for example, scan a QR code with your phone. But if you set it to cross-platform, the opposite is true. Now for our purposes, for passkeys, either could be the case. Either is a valid option. So we need to set Authenticator attachment to No Preference, and I also recommend setting Require Resident Key to True.

Starting Passkey Storage0:32

So we need to set Authenticator attachment to No Preference, and I also recommend setting Require Resident Key to True. This will inform the Authenticator that it should store a little bit of information about the user in the Authenticator itself, which is going to allow for an even more seamless login flow a little later down the line. Okay, enough about options. Let's actually get this passkey saved in our database. We'll head to our CRUDPasskeyController, and we're looking at the store method here, which I think makes the most sense for this particular request. Now at the end of the day, what we actually need to do is take the data the user has given

which I think makes the most sense for this particular request. Now at the end of the day, what we actually need to do is take the data the user has given us, validate it, verify it, make sure it's actually okay to store, and then obviously save it to our database. And the long and short of that is that we're going to get an Authenticator attestation response validator. It's important that you understand that attestation is linked to registration, whereas assertion is linked to authentication. Keep those two terms in mind because they're similar, and if you get mixed up, you're going to see errors.

Keep those two terms in mind because they're similar, and if you get mixed up, you're going to see errors. But back to what I was saying, we want an Authenticator attestation response validator that we create and then call check on. And essentially the job of this controller is to pass the required parameters to check so that the WebAuthn framework package can do the work. We're going to do this in backwards order. So the first thing I'm going to focus on is this request parameter. Now technically speaking, at least as of the time of this recording, we can pass the entire request object for this parameter and it will work, but that soon won't be supported.

Now technically speaking, at least as of the time of this recording, we can pass the entire request object for this parameter and it will work, but that soon won't be supported. So instead you should call request()->getHost() to satisfy this requirement. I recommend doing that from the get go because otherwise you're going to have breaking changes later down the line. All right, one parameter done, let's move on. The next parameter we need to pass is an instance of PublicKeyCredentialCreationOptions. You've heard of that term before. In fact, that's exactly what we created in the last episode. The WebAuthn framework needs an instance of these options to check that they match up.

Storing Options in Session2:34

In fact, that's exactly what we created in the last episode. The WebAuthn framework needs an instance of these options to check that they match up with what we've been sent from the front end. The easiest way to do that is to store these options in the session and then retrieve them in our other controller. So let's go ahead and do it. Seeing as these options only need to exist for one additional request, we could use flash for this. So session::flash and let's say pass key registrationOptions and then we'll pass the entire object right as the second parameter.

So session flash and let's say pass key registrationOptions and then we'll pass the entire object right as the second parameter. Let's go ahead and copy this key and then we'll jump back into our standard CRUD controller and then we can set the publicKeyCredentialCreationOptions parameter to session get passing in the key that we've just defined, pass key registrationOptions. Okay, two parameters done. Now onto the main parameter of the day, the authenticatorAttestationResponse. This parameter is built up based on what the user passes from the front end, but there are a few steps involved in doing that. So let's work through them.

Validating and Deserializing Credential3:38

are a few steps involved in doing that. So let's work through them. The first thing I'll do is a little bit of data validation from the front end. So let's say request validate. We'll have a field called passKey here and I'll say that it's required and that it should also be a valid JSON string. We don't need to do too much validation because the WebAuthn package is going to do some additional validation for us. But once we have that in place, well, we need to deserialize this JSON string into an object that WebAuthn understands.

But once we have that in place, well, we need to deserialize this JSON string into an object that WebAuthn understands. Thankfully we don't have to worry about doing that manually because the package actually provides a deserializer that we can make use of. We need to new up an instance of a WebAuthnSerializerFactory, which expects an AttestationStatementSupportManager. You can just call create on that because we're not too interested in attestation for our particular use case. We can call create in order to build the factory and then there's a method called deserialize which is exactly what we want to create this object.

We can call create in order to build the factory and then there's a method called deserialize which is exactly what we want to create this object. So we're going to pass in data pass key. We're interested in creating a PublicKeyCredential object, which is just here. That's a class provided by WebAuthn. And as a third parameter, we have to say what the format is. In this case, it's JSON. So we'll pass that as a string. And if everything goes to plan and the data is valid, we're going to be returned a PublicKeyCredential.

And if everything goes to plan and the data is valid, we're going to be returned a publicKeyCredential. In other words, we're going to be returned an instance of this class here. To ensure we have nice IDE auto completion, I'll add a little doc block at the top here and say that this variable is an instance of publicKeyCredential. And because publicKeyCredential is used both for registering and authenticating with WebAuthn, with a pass key, we just need to do one little check. We essentially need to say, look, if the publicKeyCredentials response parameter is not an instance of, so let's use a little exclamation mark at the start, is not an instance of the AuthenticatorAttestationResponse, remember how I said that attestation goes with registration.

an instance of, so let's use a little exclamation mark at the start, is not an instance of the AuthenticatorAttestationResponse, remember how I said that attestation goes with registration and assertion goes with authentication? Well in this case, we're registering a pass key. So we need to make sure the response is an Attestation rather than an Assertion. If that's not the case, then we could do something like, look, return to the root login. Now technically speaking, unless someone's trying to hack us, this line of code here should never, ever happen. But it's good just to have it in place so that we catch those edge cases and we make sure everything works smoothly.

But it's good just to have it in place so that we catch those edge cases and we make sure everything works smoothly. Now at this stage, if you go back and check the signature of the check method, the first parameter is an authenticatorAttestationResponse, which is exactly what we've just verified this response is here. So we'll pass that in. AuthenticatorAttestationResponse is the publicKeyCredentialResponse property. And with that, we've successfully filled out the check method. If the check method is successful, in other words, if there are no issues that have come from the front end, then this will return an object that we refer to as a publicKey.

Handling Check Exceptions6:44

If the check method is successful, in other words, if there are no issues that have come from the front end, then this will return an object that we refer to as a publicKeyCredentialSource. This is what we use to store the information in our database. However, it's highly possible that something went wrong, and if so, this check method is going to throw an exception. So let's go ahead and wrap it with try catch. It will throw a Throwable, which I'm not actually interested in too much for the purposes of this particular series. But what I will do is inside, throw a ValidationException with messages, and I'm actually

this particular series. But what I will do is inside, throw a ValidationException with messages, and I'm actually going to assign this to the name property so that we can display it on the front end. Let's just say the given passkey is invalid. Now quick thing while we think about it, the manage passkeys.blade.php file has the input error for name under the create passkey error bag. So when we create this ValidationException, I also need to assign the correct error bag to it so that we'll actually be able to see this in the front end. At this point then, we're ready to grab the request. We'll find the User who's authenticated.

At this point then, we're ready to grab the request. We'll find the User who's authenticated. We're interested in the passkeys relationship, and we're going to call create. And then we need to pass a few different properties in here. So one is the name. This is the friendly name that the User gave the passkey. We should accept that inside the validation that we do at the top here. Let's say name required string with a max length of 255 characters. And then down the bottom, we can just say data name. You might also want to think about making that unique for the names across each User,

And then down the bottom, we can just say data name. You might also want to think about making that unique for the names across each User, but that's not something I'm going to focus on in this series. We also need to pass a credentialId. The credentialId comes from our publicKeyCredentialSource, and you're looking for the publicKeyCredentialId property. Finally, we need to pass in that JSON, the data property that we created, and that is literally the publicKeyCredentialSource object that we've just created and verified. Because we've already got this filled out as a JSON cast inside the model,

verified. Because we've already got this filled out as a JSON cast inside the model, it will automatically be serialized to JSON for us. So we don't need to worry about that for now. However, one thing we will need to do is add these keys to the fillable property of our model. So let's jump into Passkey. And here at the top, I can say fillable equals, and I'm going to set, first of all, the name, I'm going to set the credentialId, and I'm going to set the data. Our last step is going to be to return the User to the correct page.

Wiring Frontend Form Submission9:10

the name, I'm going to set the credentialId, and I'm going to set the data. Our last step is going to be to return the User to the correct page. So I'm going to return to profile.edit, and I'm going to add that little fragment on managePasskeys, just so it takes the User to the correct part of the page. Nice, so now let's turn our attention to the front end, get this form wired up, so that we can see the whole process in action. I'll start in the managePasskeys.blade.php file, and I'm going to fill out the action here, which is obviously where the form will actually take us. I need to set it to a route, which we've not actually created yet, but it's essentially going to be passkeys.store.

I need to set it to a root, which we've not actually created yet, but it's essentially going to be passkeys.store. Let's add that root now. So we'll jump into web.php, and above delete here I can say Route::post forward slash passkeys. We want to point to the PasskeyController store, and we'll call it passkeys.store as we've already defined in the action here. Nice, I've already got the method set as post, which is absolutely fine. Everything else should work here. The last thing I'm actually going to do is pass the current element in Alpine

Everything else should work here. The last thing I'm actually going to do is pass the current element in Alpine to our register function. That will essentially just be the form element that this is based on. And now if we go to app.js, we can take that into account. So this is going to receive a form. And just here I'm going to set up an event listener. So form.addEventListener, and I want to hook into the formdata event. formdata is really cool. It's a hook that you can use that will be created just before a form is submitted,

Form data is really cool. It's a hook that you can use that will be created just before a form is submitted, and it allows you to alter and mutate any of the form data, even from a standard HTML form, and then send that off to the server. So in the closure here, I'm going to destructure the event and ask for the formData property. And then in the actual body of this closure, we can say formData.set, and I'm going to set a passkey property, which is exactly what our backend is expecting, which will be JSON.stringify, passing in the passkey that we created here.

which is exactly what our backend is expecting, which will be json.stringify, passing in the passkey that we created here. And that's everything that we need to do in order to mutate the form ready for sending to our backend. It will already include the name because we have that as an input here inside the actual form itself. So now we can say form.submit. And that part is important because obviously, originally, we've prevented the form from being submitted to make our options request. So this completes the circle, and hopefully now, we should be ready to test it out.

Testing and Homework Fix11:34

originally, we've prevented the form from being submitted to make our options request. So this completes the circle, and hopefully now, we should be ready to test it out. Let's jump to the front end. So let's give our passkey a name, my passkey, hit Create. We can save the passkey, and it's going to go to our backend, submit the form, and you can see right here, our passkey has indeed been saved. We have a successful implementation where not only can we create a passkey, but we now see it in our frontend, and it's ready for use during the login process. Right, let's talk homework. If we go ahead and create a passkey without providing a name, I can click Save.

Right, let's talk homework. If we go ahead and create a passkey without providing a name, I can click Save. But of course, it's not going to actually create that passkey because validation will fail in the second step. However, the problem is a passkey has already been created. Now, we didn't actually see any validation error, and that's because when we do the validate here, we should actually validate with bag, and then we need to pass the correct bag from manage passkeys, which is create passkey.

and then we need to pass the correct bag from manage passkeys, which is create passkey. So if we were to do that, let's try again and make sure this actually works. Create passkey, I'll create a new passkey without a name. And yeah, we say the name field is required. But as I say, it's already a little late in the process because the passkey has already been stored in our authenticator. So really what we want to do is perform the same name validation here inside our PasskeyController API register options method so that we never even ask the authenticator to create a passkey

inside our PasskeyController API registerOptions method so that we never even ask the authenticator to create a passkey unless the name is valid. So how about you go away and you add that validation to the API, and then you update AlpineJS and the frontend to ensure that the validation shows under this field as and when the name is incorrect. That should solve those problems, and we should be ready to move on. So I'll see you in the next episode after you finish that exercise.

So I'll see you in the next episode after you finish that exercise.

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