So the first thing we have to do is we have to enable this link click here to reset password. Right now it redirects us back to login. So let's go ahead inside of our routes.ts file right here. Inside of our auth routes we have to add that new link so let's add slash auth slash new dash password like that so that's only going to be available for logged out users because logged in users will have the settings to change their password So it's different from this new verification where it can be used both by logged in and logged out users. That's why this is in public route.
So you can be accessed if the user is already logged in. Great. So just confirm that this slash out new password is exactly the same as you're sending inside of LibMail right here. So send password reset email should use slash auth slash new password and then append a token query here. So make sure that this exactly matches what you just wrote inside of this new auth routes and then if you click inside of your mail here it should lead you to a 404 page but it should not redirect you.
Great! Now let's go ahead and let's go inside of the app folder auth and let's create a new folder, new password. Then inside create a new file page.tsx and export default new password page and a div new password. And there we go we now have a new password page perfect so obviously as always we're now gonna go ahead and change this to use the new password form and now let's go ahead and let's copy the existing which one should we use How about we copy inside of components auth, let's copy the reset form. I believe that one is the simplest.
So copy the reset form, paste it inside of auth, rename the copy to new password form. Make sure that you are inside new password form so you can close the other one. So confirm you are inside new password form. And in here go ahead and rename this to new password form like that. And then you can go back inside of app auth new password where you have this error and let's import new password form from components new password form and it will look exactly the same as the one we just created for the resetting of the password.
So now what we have to do is we have to go ahead and modify it. So first thing that I want to do is go inside of my schemas right here in the index. Let's copy this schema and let's rename it to new password schema and let's change this to be a password and let's go ahead and give this a value of minimum of 6 and then the message will be minimum of 6 characters required or what did we write before minimum 6 characters required. All right, exactly that. So just the password field for the new password schema like that.
Now let's go back into the new password form and let's import instead of reset schema, new password schema. So we're going to get errors wherever we are using the old one. So in here, in here, and in here, let's replace that with new password schema and now we have an error for the default values so that should be the password of course and inside of the on submit yes we have an error we're gonna fix that later. So now what I want to do is I want to modify the card wrapper here. So the header label instead is going to be enter a new password.
And this can stay the same. Back to login. And we don't need to show socials here either. Inside of the form field, let's change the name for this one to be password. Let's give this a form label to be password as well.
And let's go ahead and change the type to be password. And the placeholder let's just give it five stars actually six stars like that and let's change the submit button to say a reset password like this there we go What we have to do now is we have to fetch the token from our URL. So inside of your URL you should have the token if you clicked here from the reset password. So your token should be appended just like it was for the verification. If it's not, go ahead and double check inside of your mail that you are sending send password reset email with the token appended and confirm in the reset action when you await send password reset token that you do a proper order of email and token if you want to you can also add a console log of the password reset token to see if something is going wrong inside of it and then debug from there.
Great! So let's go ahead and let's import useSearchParams from next navigation like that and let's go ahead and add it here. So const searchParams is useSearchParams like this and then we can extract the token to be searchParams.getToken and we already know why it's token is because it's inside of our URL so just double check that you didn't misspell that inside of your URL it should be token like this. Great! So now we have our token here.
And now what we have to do is we have to create a new server action to handle this. So let's go inside of actions, new one, new password.ts, like that. Mark it as use server. And let's go ahead and export cons new password to be an asynchronous function, which accepts the values, which are a type of import everything as Z from Zod. So we need that and let's import new password schema from schemas.
So then we can write values is a type of z.infer type of new password schema. And let me just not misspell typeof and we're also going to pass the token from the url which can be string or null like that and immediately if we don't have a token let's go ahead and return an error missing token like this great Now we can use this new password in place of our new password form. So instead of components out we have a new new password form here. And instead of using reset action let's use new password import and let's import new password and then we can replace the on submit function to use the new password and besides sending values as you can see it also accepts the token so add a comma here and pass in the token which we extract right here from the search params like this perfect and we're going to fix this in a second So now I want to go ahead back inside of the new password here and let's go ahead and let's validate our fields. So const validated fields is going to be new password schema dot save parse passing the values.
If we didn't get a success from validated fields. So just make sure you put an exclamation point here. Let's go ahead and return an error. Invalid fields. Like that.
And then we can extract the password from validated fields.data. And now let's do some token validation here so const existingToken is going to be await getPasswordResetToken by token so make sure that you import getPasswordResetToken from data password reset token And then we can use our prop token, which we pass in as the second argument here. And we can see if we can find that token inside of our database. So if there is no existing token, we're going to return an error, which is simply saying invalid token. Like this.
And now let's go ahead and check if the token has expired that's gonna be await get sorry no that's gonna be new date right Existing token.expired is less than the current new date. So if it has expired, we're going to return an error. Token has expired like that. And it's not .expired, it's .expires like this. And let's now go ahead and check the existing user which matches the password we are trying to reset for so await get user by email from data user so make sure you import get user by email from data user so make sure you import get user by email from data user we're gonna use we're gonna use existing token dot email like this if there is no existing user let's go ahead and return an error saying user actually email does not exist so for whatever email we sent this token to reset the password no longer matches what we have in our database.
Great And now what we can do is we can finally hash the new password and update the user. So for that we're going to need to import bcrypt. So you can use either bcrypt or you can bcrypt or you can use bcrypt.js. I'm going to use js because I found to have less problems with that so let's go ahead and let's hash the password in here so const hashed password is going to be await bcrypt.hash password which we pass from our fields and the salt is going to be 10. And then await database, so make sure you import database from lib database, .user.update where id is existing user.id and data is simply going to update the password to be the new hashed password.
And then await database.passwordResetToken.delete where id matches the existing token.id. And finally, we can return a success saying password updated. There we go and because we added this success here we no longer have that error here for data.success So if I'm correct this should be working just fine but I believe my token might have expired already So here's what I'm gonna do first. First let's clear our URL. So let's just try what happens if I do new password without any token at all.
So technically we could already you know redirect the user from here but I want to see explicitly what happens if we try to reset a password. There we go missing token So we are not allowing this to reset any password. Great. And now let's go ahead and let's use the email from Resend to send a new email here. So let's copy this.
This one. Tutorial mailing, send reset email. And this should send me a new email any second. Let's go ahead and check my inbox. There we go.
Reset your password and when I click here it's asking me for a new password. So my previous password was 123456. So this time I'm gonna try 654321. Let me just confirm that I entered that correctly 654321. I will click reset password and that should just tell me that I have successfully reset my password.
There we go. So if I go back to login now, and if I try this with my old password, 123456, like this, it should tell me invalid credentials. Perfect. But if I try 654321, it should allow me to log in because I already have this email verified regardless if you have the email verified or not right you will not even get that message otherwise. Perfect!
So we successfully implemented the forgot password functionality inside of our application and I just want to confirm directly in my Prisma Studio that this is happening. So I'm gonna close everything besides my user here and let's go ahead. I just want to show the password. I don't want anything else like this. So I just want to take a look at the hashed password here.
You can see how it looks now. So what I'm going to do, let me just, I have too many tabs open. So what I'm going to do now is click forgot password again I'm gonna send another image another message to this so send the reset email and there we go so this one took a bit longer for me but still it successfully reset sent the reset email and there we go I have a new password here So let me click this link and now we're going to focus on the password hash right here and see if we can notice it change. So now my password is 654321, So I'm going to change this to a new password. Like that.
And click reset password. And once this succeeds, let's go ahead and refresh our password hash. I don't know if we're going to be able to notice any change here I'm not sure there we go you can see how it clearly changed to a new hash perfect so it's officially updated in the database and if I try this again with my old password 654321 I should get invalid credentials but if I try new password I should be logged in inside of my application. There we go, we successfully implemented the reset password functionality. The last thing that we have to do regarding this login screen right here is two-factor authentication.
Great, great job!