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

Why options are needed0:00

Quick check before we get started, how did you get on with the homework? You should have something like this where if you click the remove button, the page is reloaded and the passkey is deleted. And I implemented just a little fragment at the top as well to jump to the correct section of the page. So if you managed that, great job, well done. If you struggled, don't worry, go and give it another go. And if you don't want to give it another go, well, you can clone the episode to start branch, which essentially will put you in exactly the same place where we are now. You would be completely forgiven for thinking that we just need to call some JavaScript API to create a passkey on device and then pass that data to our server in a form to store it in the database. And essentially that is going to happen, but there's a step we need to do first, which is creating the

pass that data to our server in a form to store it in the database. And essentially that is going to happen, but there's a step we need to do first, which is creating the passkey options. Let me illustrate why that's important. Imagine you go to a restaurant. The waiter, he takes you to your table, he sits you down and immediately he says, right, what are you having? Well, you've not had time to think, you've not seen the menu, so perhaps you say, I'll have a burger. No, we don't have burgers, sorry. Steak and chips? No steak, I'm afraid. What about a pasta dish? No, we're not an Italian, we don't do pasta. Well, you wouldn't be staying at that restaurant for very long. Now, in fact, when you go to a restaurant, the waiter sits you down and he hands you a menu where you can see what options are actually available. Now, to bring that back in line with passkeys, passkeys

Set up Sanctum API1:24

a restaurant, the waiter sits you down and he hands you a menu where you can see what options are actually available. Now, to bring that back in line with passkeys, passkeys are just a small part of a standard known as WebAuthn, and WebAuthn covers so many different things. For example, USB security keys that you plug into your computer to verify that it's you, that falls under WebAuthn. And so our server needs to tell the browser and the authenticator what we actually want to support with our particular API on our particular website. And we do that using an asynchronous options request. So in this episode, we're going to focus on that options request. Because this request needs to be asynchronous, I'm going to use php artisan install:api to configure Laravel Sanctum in our application so that we can have some API routes. And yes,

Because this request needs to be asynchronous, I'm going to use php artisan install:API to configure Laravel Sanctum in our application so that we can have some API routes. And yes, I'll run the database migrations now. In our User model, we can add the hasApiTokens straight from Sanctum, and in our bootstrap/app.php file in the withMiddleware closure, I'll call middleware('stateful') to ensure we don't have to worry about personal access tokens, and we can use the session inside our API routes. Next, I'm going to create a controller here. So php artisan make:controller, and I'll put it under an API namespace. So API/PasskeyController. Let's open that one up and let's create ourselves a public function inside that PasskeyController. I'll call it registerOptions. Inside our API.php file, I'm going to refactor this user endpoint,

Install WebAuthn library2:52

up and let's create ourselves a public function inside that PasskeyController. I'll call it registerOptions. Inside our api.php file, I'm going to refactor this user endpoint, which we're not using. So I'll say /passkeys/register. And then let's point to our new controller. We'll remove this closure and I'll say PasskeyController, making sure to pull in the API variant. And then obviously we'll point to registerOptions. Creating these options and validating the passkey is by nature complicated. And if you get it wrong, you could be opening up a security hole in your app. So it's not recommended that you roll your own code here. Instead, you should reach for a package that does this for you. And the WebAuthn framework package is the one I would recommend for php. You can install it using composer, which is always nice. So let's go ahead and copy this command.

for you. And the web-authn framework package is the one I would recommend for php. You can install it using composer, which is always nice. So let's go ahead and copy this command here and we'll execute it to get it installed. You'll note too that after 4.8.0, it also requires these various components from symfony. Well, as of recording, we're using 4.8 and soon, I imagine 5.0 will be available. So in order to future proof this series, we're going to be installing these packages too. In case you didn't know, you can actually specify multiple packages with a space in composer and it will install all of them at the same time. With our web-authn package installed, we can start defining options. And this is going to be an instance of a new PublicKeyCredentialCreationOptions object, which is a mouthful and is also provided by the web-authn library. The first thing

Build registration options4:24

And this is going to be an instance of a new public key credential creation options object, which is a mouthful and is also provided by the webauthn library. The first thing we need to pass in is an RP, which is a relying party. And this is an object that gives us information about the application itself, the name of the app, the domain name that you're on. And it will use those things to store inside the authenticator and then display the correct information to the user. So this is an instance of a new public key credential RP entity, and it's going to accept a name. Well, the name is whatever your app is called. You can usually grab that from config('app.name') and then also an ID. The ID should be the domain name of your application. So the closest thing we've got to that is config('app.url'), but you'll notice config('app.url') also includes the protocol, http://, which

domain name of your application. So the closest thing we've got to that is app.url, but you'll notice app.url also includes the protocol, http://, which we don't want and isn't supported by web authn. So to solve for that, we can pass the URL and after passing the URL as a second parameter to this php function, we can say that we're only interested in the URL host. This is essentially going to strip the protocol for us and we'll just be left with that domain name. Another interesting tidbit here, in case you're working with maybe multi-tenancy and you have lots of different subdomains, if you use the top level domain in this ID, all subdomains are also supported. So that's a cool one to keep in mind. Okay, let's move to the next argument. We have to pass a challenge. Interestingly enough, this is not actually used during the registration process, but

a cool one to keep in mind. Okay, let's move to the next argument. We have to pass a challenge. Interestingly enough, this is not actually used during the registration process, but it's still a required field. You can pass essentially a call to str_random here in Laravel, which will absolutely suffice. str_random is cryptographically secure, so it will solve all the needs you need for this step in the process. And then the third parameter we're going to pass is a User object, which is actually an instance of a public key credential User entity. This is going to provide us information about the User who's actually storing the pass key, which we can obviously grab from the request object. So let's make sure we've injected that at the top. And then inside here, there are three parameters. The first is the name. This is not the User's name. It's actually a unique piece of information.

injected that at the top. And then inside here, there are three parameters. The first is the name. This is not the user's name. It's actually a unique piece of information that you use to identify the User, not the user's ID, but in most Laravel apps, the user's email or the user's username, depending on how you've configured. So let's go for request->user. And in this case, I'm reaching for the user's unique email. We also have to pass in an ID. Now you might think, well, I'll just reach for the email again. No, you can't do that. The ID cannot contain any personal information. So it would be best just to use the request->user->id. And then finally, we need to reach for the displayName. This is where you would use perhaps the user's full name, something friendly that is memorable and is personal to the User. So in this case, we'll say request->username, which will work.

Fetch options with Alpine7:31

where you would use perhaps the User's full name, something friendly that is memorable and is personal to the User. So in this case, we'll say request username, which will work for most Laravel apps out of the box. Now I'm getting some yellow squiggly lines here just because my arguments aren't quite in order. So if I refactor with phpStorm, you can see that's fixed and we have a valid object. Last, all we have to do is return these options. And because Laravel is intelligent and because this object implements the correct contracts, it's actually going to automatically serialize itself to JSON and get passed back to our front end. So let's turn our attention to the front end and wire it up to actually call that API route we've just created. I'm going to use AlpineJS here. Seeing as we have it installed and ready, we might as well make use of it. And inside my app.js file, I'm

that API route we've just created. I'm going to use AlpineJS here. Seeing as we have it installed and ready, we might as well make use of it. And inside my app.js file, I'm going to add an event listener for Alpine in it, where I can go ahead and define a reusable piece of data that we can attach to our form. So in this case, I want to say Alpine.data and let's call it registerPasskey, which of course is going to go ahead and return as an object that we can assign data to. In this case, why don't we have a method called register? Here we go. And inside register, the first thing we want to do is make a request to go and grab those options. So let's say const options equals and why don't we make this an async function so that we can call await axios.get and we'll want to go to /api and then let's remind ourselves of the actual route passkeys/

this an async function so that we can call await axios.get and we'll want to go to /api and then let's remind ourselves of the actual route /passkeys/register. There we go. We'll drop that in place. And for now, let's go ahead and console.log the options.data that we received from the endpoint just to make sure everything's working. So to actually use this Alpine data, I'm going to copy the name here. We'll go to manage passkeys.blade.php and I'll create an x-data attribute set to registerPasskey. Then I'm going to listen for the submit event. I'll prevent the default action, which would be submitting the form to the server. And I'm going to call the register function that we've defined inside app.js here. Okay. Let's see if it works. So I'll refresh the page. I'm going to come down and hit create passkey. And sure enough, you can see in the console.

defined inside app.js here. Okay. Let's see if it works. So I'll refresh the page. I'm going to come down and hit createPasskey. And sure enough, you can see in the console we have these options from the server. Here's the challenge. We have the relying party, passkeys.test and Laravel, the User object. And side point, if you're thinking, well, why is that ID not numeric? In order to conform to WebAuthn standards, that ID has to be encoded. Base 64 URL encoded, I believe. So essentially, the WebAuthn library that we've installed is doing that work for us and we don't have to worry about it. Well now that we have our options available to us, we can call the JavaScript APIs necessary to create a passkey in our authenticator of choice. But again, just like the server side, there's a lot of boilerplate involved, which we really don't want to have to write. Thankfully,

Create passkey in browser10:30

to create a passkey in our authenticator of choice. But again, just like the server side, there's a lot of boilerplate involved, which we really don't want to have to write. Thankfully, there are some really cool JavaScript packages available that take all the boilerplate out of it. Let me show you one. Simple WebAuthn is an amazing package, but it's built for Node.js. They have this server package and the browser package, but there's nothing to stop us using their browser package with our php backend. And that's exactly what we're going to do. So I'll copy this here. We'll jump to our terminal, npm install it. And whilst it's installing, you can see in the documentation they have a startRegistration function that we can make use of. So with it installed, how about we remove console.log from our register function and let's say const passkey equals and I'll await startRegistration.

function that we can make use of. So with it installed, how about we remove console.log from our register function and let's say const passkey equals and I'll await startRegistration. I've imported startRegistration from the package here at the top and we have to pass startRegistration, the options that we've received from the server. So that will be options.data. And then how about we go ahead and console.log that passkey out so we can see what's happening. Let's head down to managePasskeys, hit createPasskey, and we get an error. WebAuthn is not supported in this browser. Now, if you've got a modern browser, which I imagine you have because you're a developer, passkeys are supported. So the only reason that this is showing is because we're on HTTP instead of HTTPS. You have to be in a secure context to use passkeys. If you're using Laravel Valet, Laravel Sail, Laravel

that this is showing is because we're on HTTP instead of HTTPS. You have to be in a secure context to use passkeys. If you're using Laravel Valet, Laravel Sail, Laravel Herd, it's very, very simple to get started with SSL. We're going to go to the terminal and I'm going to use herd secure here to say herd secure. It will ask me for my password. And once that's done, you can see it has been secured with a fresh TLS certificate, which means if I refresh, I'm now automatically on HTTPS. Laravel Herd for the win. Okay. So now we should be able to come down to manage passkeys, hit create passkey, and you'll see the modal pops up for me from 1Password asking me to save a new passkey. If you're not using 1Password, it's probably going to be the browser that pops something up and says, do you want to save this to your device? Go ahead and click save. And here you can

not using one password, it's probably going to be the browser that pops something up and says, do you want to save this to your device? Go ahead and click save. And here you can see that passkey appear in the console. How cool is that? If you jump into your authenticator, you should see the passkey there as well. So here's my username. I can see when the passkey was created and I can see the domain name that's actually being used to register the passkey. A little more homework for you. There's another argument you can pass to the creation options called authenticatorSelection. This is a new instance of authenticatorSelectionCriteria, and you'll see that it accepts a parameter called authenticatorAttachment. If you actually were to go ahead and fill that out, there are constants available on the authenticatorSelectionCriteria that you can set. So narrow it down to attachment.

If you actually were to go ahead and fill that out, there are constants available on the Authenticator selection criteria that you can set. So narrow it down to attachment and you'll see that you have platform, crossPlatform, and noPreference. Your homework is just to try out all three of those options and see how it affects your passkeys on the front end. So that's stage one of creating a passkey complete. In fact, we are creating a passkey. It's being saved. We're just not doing anything with it. So in the next episode, we need to take that passkey, send it to our server and store it in our database.

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