Auth API overview0:05
Let's add a way to authenticate ourselves into the shop so we can place orders and retrieve our order history. I provided a backend with three API calls that will utilize in the front end to register, to log in and to retrieve our user details. Let's take a look at the parameters, the API calls take a register call, takes an email, a password and a name. And if we execute the request, we'll get a token back that's generated by Laravel Sanctum.
And if we execute the request, we'll get a token back that's generated by Laravel Sanctum. If registration fails, we'll get our errors back. In this case, the email has already been taken. Next we have our token route, which is basically a login route. It takes an email and a password, and if we send a request and authentication succeeds, we'll get our token back. If you want to access authenticated routes, for example, to get the User details, we need to set this token.
If you want to access authenticated routes, for example, to get the User details, we need to set this token as a Bearer token in our authorization header. We can do so as follows: if we send a request, an authentication is successful. In this case, we'll get our User details back. If we don't send an authorization header or if the token is invalid and we send a request, we just get a message "unauthenticated" with a 401 response code.
and we send a request, we just get a message an authenticated with a 401 response code. If we briefly take a look at the backend code, we'll see our register route and our token route, and in this group we apply a middleware called auth:sanctum, meaning that the token will be validated and if it succeeds, we are authenticated. In this group, we have our UserController that will simply return the currently authenticated user. If you take a look at the AuthController on registration,
that will simply return the currently authenticated User. If you take a look at the AuthController on registration, we do a simple validation on the name, the email, and the password. If it succeeds, we will create a User and then we will create our API token using Laravel Sanctum to get our token or to log in. We do the same. We validate our email or password, we look up our User, we check to see if the password
Create User store1:49
or password, we look up our User, we check to see if the password that has been entered matches the password stored in the database. If not, we throw a validation exception and otherwise we return our token back. Let's build our front end together. I already provided a login page and a registration page. So let's get started. With registering a User, the first thing we'll do is create our user store.
So let's get started. With registering a User, the first thing we'll do is create our userStore. Let's export const userStore = definedStore. Let's call this user and let's create an optionStore. Let's initialize it with an empty state. And then let's move on with the actions. The first action we need is a registerAction. It takes a name, an email, and a password. And in here we'll add a registration logic.
Implement registration flow2:38
It takes a name, an email, and a password. And in here we'll add a registration logic. Let's open up a register page. A register page is basically a simple form that when submitted will execute the handleRegister function. Let's get started by initializing our userStore. Don't forget to import it and in our handleRegister function. Let's say userStore.register
or handle register function. Let's say user store register and we'll pass our credentials. Value, name, email, and password. Let's zoom back into our store and create the registration logic. The first thing we'll do is create our API wrapper for the authentication. In my case, the backend lives on view, state management backend do test
In my case, the backend lives on view, state management backend do test and we'll initialize the wrapper to API auth. Let's import Monday and then let's get started. In a register action, let's say auth.post.register. Let's pass our data name, email, password. Let's store the result in a variable registrationResponse and let's await the result. And for good measure, let's return success through.
and let's await the result. And for good measure, let's return success through and we switch to our front end and we try to register and we take a look at our network traffic. We should see an API call fire to the register endpoint. And if all went well, we will get a token back. Next. When we try to register again using the same credentials, we should get an error. And there we go, because the email address has already been taken to provide a good user experience,
And there we go, because the email address has already been taken to provide a good user experience, let's display these errors in our form in our User store. Let's get rid of this comment and wrap our registration call into a try catch, and if it succeeds, we'll say return success, true and otherwise will return success, false. And let's return the body. We can find that in $body and this body will contain the errors.
We can find that in e body and this body will contain the errors and the message that Laravel gives back to us. Next in our registration page, let's say $registrationResponse equals, let's clean this up a bit and then we can check if $registrationResponse success equals true, everything went fine and else will have to display the errors on the form. I already provided two variables, $message
and else will have to display the errors on the form. I already provided two variables, message and errors that we can use for this use case. Let's say message.value equals registration.response.body.message 10. Let's take the errors. We can say errors.value equals object.assign(registration.response.body.errors), and we have to object.assign it to keep the errors reactive.
and we have to object, assign it to keep the errors reactive. And finally, we have to await the response so the promise will get resolved correctly. Now let's try to register again using the same credentials and our backend will reject the registration with the error that the email address has already been taken and our form is updated accordingly. That's great. When the registration succeeds, let's take the token and store it in our state.
Store token and fetch user5:56
That's great. When the registration succeeds, let's take the token and store it in our state. So let's create our state, let's call it token, initialize it to null. And here we can say this token registration, response token create our action setToken token is out token equals token. Whenever we set the token, let's also fetch the currently authenticated user as follows.
let's also fetch the currently authenticated user as follows. Let's say this fetchUser. Let's create an async action called fetchUser. And in here we'll fetch the details. Let's start by creating a new wrapper called User. And instead of going to the old endpoint, we'll go to the user endpoint. Let's do our API call. We'll say await user.get. And in here we'll pause our options.
Let's do our API call. We'll say await user.get. And in here we'll pause our options. We need to pause in our authorization header, let's say headers.authorization Bearer, and our authToken. Let's capture the response. Let's create our userState, initialize it to null, and then we'll say this.user equals response. Now whenever we register, we go to the network inspector, we'll see.
Now whenever we register, we go to the network inspector, we'll see. We do a call to the user endpoint, and if you take a look at the headers, we'll see. We send an authorization header with our saved bar token, and in the response we'll get our user details. Now, if we open up the dev tools and we navigate to the PIA store user, we'll see we have our art token here and our user is available in the store.
Implement login flow7:44
we'll see we have our artisan token here and our User is available in the store. Great. Finally, let's go to the register page and when registration is successful, let's redirect to the homepage router.push, and let's go to the homepage. Next up, we can build the login flow. We can actually copy this entire logic. We can go to the login page, and in our handleLogin, we can paste the things we copied from.
and in our handleLogin, we can paste the things we copied from the registration flow. Let's rename a few things, loginResponse, and instead of awaiting the register call, let's call login. We only need an email and the password, and let's make sure router is available as well. Next, let's jump into the User store and create our login function. Again, the logic is mostly the same.
and create our login function. Again, the logic is mostly the same. So let's start by copying register. Let's remove the name and let's actually rename it first. Let's say loginResponse. We'll await a call to login. We'll pass in the email and the password; the name is not required. If login succeeds, we will set our token. I will respond with the success of true and
If login succeeds, we will set our token. I will respond with the success of true and otherwise we'll respond to the success of false and get the response body back so we can display an error in the form. Let's go back to the login page. Let's make sure we have our User store. And now let's try to login. Turns out I made a mistake and we need to change the login and point to token.
Turns out I made a mistake and we need to change the login and point to token. Now when I try to log in with the wrong credentials, we should see a nice error appear. There we go. And when I log in with the correct credentials, we should redirect to the homepage. Great. We can validate we are logged in by opening the dev tools, going to user, and we can see we have an all token and my user is loaded. Let's update our nav bar to remove the login
and we can see we have an all token and myUser is loaded. Let's update our nav bar to remove the login and register button when we are logged in, because this makes no sense. Let's open up the nav bar, wrap this in a template and let's say if we don't have a user in our userStore and otherwise, let's show a button that links to profile with the userStore.user.name. Let's import and create our userStore. And now we should see the nav bar update. Great.
Persist auth with router guard9:58
Let's import and create our userStore. And now we should see the nav bar update. Great. However, we do have one massive problem. Whenever we refresh, the state is not preserved and we have to log in again, so let's fix that. Now let's open up our userStore. And whenever we set a token, let's also make sure to store this in the localStorage. We'll say localStorage.setItem('token', JSON.stringify(token)).
We'll say local storage, setItem, token, and let's say JSON.stringify token. Let's also make this function asynchronous and let's await the result of fetchUser and let's only fetch the user if we have a token. So now when we log in and let's open up the dev tools and take a look at application local storage. And in here we'll see our token being stored next. Whenever we refresh or
And in here we'll see our token being stored Next. Whenever we refresh or whenever the application initializes, we should take a look if our localStorage has a token and then we can initialize our userStore. And a great way to do this is actually using a thing called a router guard. We can say router do. Before each we'll create an asynchronous guard and this guard gets executed.
Before each we'll create an asynchronous guard and this guard gets executed before visiting any route in here. We'll check if you need to initialize our userStore. So let's start by saying const userStore = useUserStore(). There we go. Don't forget to import it. And let's say something like if (userStore.didInit), and if (!userStore.didInit), actually let's say userStore.init(),
did in it, and if you didn't in it, actually let's say user store in it, and let's wait the result. Now let's create, or in it action, let's go to action. Let's say async in it, let's create or did in IT state variable. And in here we'll check if we have a token in the local storage, let's say const token equals localStorage getItem(token), and let's say JSON.parse.
Token equals localStorage getItem token, and let's say JasonPars. There we go. If you have a token and let's say, oh, wait, this token token, and finally let's say this didInit equals true. We'll take a look. If we have a token stored in the localStorage, and if we do, we will say setToken, which in turn will fetch the user and set it on the state. Afterwards we can say, didInit equals true? So our guard only executes this once,
Afterwards we can say, did $init equals true? So our guard only executes this once, and now when we refresh the page, we are still logged in. Great. Now let's actually do something naughty. Let's go into our localStorage and let's change the token to some gibberish. Now maybe we refresh or page crashes. Let's fix this as well. And if you take a look at the console, this actually makes sense because we get a 401.
And if you take a look at the console, this actually makes sense because we get a 401 because a token being passed isn't valid, and Laravel Sanctum will just respond with a 401 unauthorized. Lucky for us, the fix is actually very easy. Let's open up our User store. And when we set the token, let's say try fetching the User and otherwise we'll say this set token, no. Now when we refresh the page, we won't have any errors left.
Build profile and logout13:02
and otherwise we'll say this set token, no. Now when we refresh the page, we won't have any errors left and the token should be cleared from our local storage as well. Finally, let's log back in and let's build our profile. Let's go to our router. Let's create a path called profile. And let's say we use the Profile page as a component. Don't forget to import it. And now when we click on our profile, we'll see our Profile page.
And now when we click on our profile, we'll see our profile page. Great, our profile page has a logout button, so let's implement that. Next, we can go to our profile page and let's create our logout action. The first thing we'll do is call our User store logout. Let's create our logout action logout. And this is actually very simple. Let's say this user equals null, and let's reset our token.
And this is actually very simple. Let's say this user equals noll, and let's reset our token. And in our logout handler, we'll also go back to the homepage. Let's say router.push home. Great now, now you press logout. We'll see we are being logged out and we are going back to the homepage. But what happens if we visit our profile page directly? Well, we get presented with a blank page.
But what happens if we visit our profile page directly? Well, we get presented with a blank page and an error in the console that reads cannot read properties of null reading name. And this actually makes sense because the profile page is trying to read the name on the User, but since we're locked out, it doesn't make any sense. It's null, and we get this error. To fix this, let's go to a router file,
Protect routes and redirect14:29
It's null, and we get this error. To fix this, let's go to a router file, and let's add a field here called meta, and let's say it requires auth equals true. Whenever our guard is invoked, we get three parameters, we get to, we get from and we get next. Let's check whether or not our next route requires auth. If to.meta.requiresAuth and we don't have a User, we will redirect and otherwise, let's just say next.
and we don't have a User, User, sort, User, we will redirect and otherwise, let's just say next. Now, redirecting to the login page is actually very simple. We just say, next login. And let's say that whenever we log in, we want to redirect back to the intended URL, so we can say redirect equals to fullPath. And now when we navigate to profile, we'll get redirected to login with a query, param redirect to profile.
And now when we navigate to profile, we'll get redirected to login with a query, parameter redirect to profile. Let's go to our login page and where we are successfully logged in, instead of always redirecting to the homepage, let's say route query, redirect, or fall back to the homepage. Let's make sure we have route available. route equals use route. There we go. And now where we log in, we should redirect back.
Route equals use route. There we go. And now where we log in, we should redirect back to the profile page, and there we have it. Before moving on to the next lesson, let's reflect a bit on what we did. We started by creating a UserStore that has an action to register a new User and to log in an existing User. Both of these actions get a token back from the backend that's immediately used to retrieve the current User details.
that's immediately used to retrieve the current user details. Finally, the token is stored in localStorage, and thanks to our custom router guard, we're able to REIT initialize our user store with this saved token. Our router guard is also able to protect certain routes, so they're only accessible whenever we are authenticated. And that's it for this lesson. I'll see you in the next one.
