All right, so now what we have to do is we have to create the actual confirmation page so that the user can click on this link from their email and that's gonna load and confirm the email here. But just before we go there I feel like I didn't do a good job explaining inside of auth.cs here we have this asynchronous callback sign in and it seems like I kind of magically knew to look at this provider here and put the credentials. So what I want to do is I want to console log the user and the account here so we can actually see what's going on inside and how I knew that I have to look for credentials here. So we use the sign-in callback to first allow any OAuth logins which is Google and GitHub to log in freely. But otherwise if it's not OAuth, so if it is credentials, in that case we search for the user and first we check if they have their email verified.
And then later, we're also gonna do two-factor authentication for credential users, because there's no need to do two-factor for Google and GitHub users, because Google and GitHub has their own two-factor authentication. So make sure that you add, or you don't have to, you can just look at the video. So I added the console log in the sign-in callback to see what we get from the user and the account here. So I'm gonna go ahead and attempt to log in using GitHub here. Let me just refresh this page to confirm everything is working.
So I will try to log in with GitHub and I'm going to see inside of my terminal if I have any useful information from that callback here. And there we go. You can see that I have logged my user. There we go. You can see my user information here and you can also see the account information and in here you can see that the provider is GitHub like this.
So I could have also used the type. Maybe that's even better. But as I said, there are many providers and not all of them are exactly, you know, reliable to not have their email verification. So now that we checked how this looks for OAuth providers, let me sign out and let me try and log in with another user. So I'm gonna go ahead and open my NPX Prisma Studio just to see what users I have inside of my account here.
Do I have any user which is already registered? There we go, I have this test user which has their email verified null and it should not have any account relations. So I know that this is email, username and password registration. And I think I know what is the password for this one. So I'm also going to go inside of actions, login, and for now I'm just going to comment out this code checking for email verification just because I want this to fail, right?
I want this to prevent us and I want to see this console log here. So I'm going to prepare my terminal here. You don't have to do this of course, I just want to go ahead and show you how this looks. So if I log in using this account, there we go. So I have an error here but if I scroll up, there we go.
So we have my user here with the email, with the email verified null, and I have an account and you can see the type is credentials and provider is credentials as well. So you could have used the type or the provider inside of here so we can now remove this console log so we could have also done user account.type is not credentials that would also work so that's where I got the provider value from if in case you were wondering about that great so let me just revert this so I want to make sure that this is now being sent here. Let me just go back inside of my code here. So I will just uncomment this out. So if I try this again, this should send an email.
Well, it will not be able to send an email here because we have not set up a domain. Great and one more thing I want to explain is the resend here. So I told you that you can only send emails to this email which you logged in with right? That is true until you add a domain. Once you add a domain you can send emails to anyone and you can also change where the emails come from because if you take a look at our mail service now you can see that I have to specify that it's coming from onboarding at resend.dev but once you add your domain which we're going to do later when we do deployment you will be able to modify this so it sends from you know for example codewithantonia.com it will be something like onboarding at codewithantonio.com right?
So no you're not stuck with this email don't worry. Great so now what I want to do is I want to go inside of routes.cs and What's happening right now is that if user tries to click on their confirmation email right here, they get redirected back to the login page. That's because they're trying to access a page called slash out slash new verification. So you might be thinking, all right, we have to add that to the out routes and that could work yes but I also want to add this to public routes because remember user will be able to change their email from their settings page and if they are able to change their email from the settings page that means that they are logged in. So the best way to, the best place to put this route is inside of the public routes.
So both logged in and logged out users can access this. So let's write slash out slash new dash verification like this and make sure inside of your mail.ts inside of libfolder mail.ts that you use that exact route so slash out slash new verification so it should match this exactly and now if I try and go inside of my mail here and open this page I should get a 404 which is a step towards while not being redirected. So now we have to actually build this page so let's go ahead inside of the app folder out let's create a new folder, new verification and inside create a page.tsx and let's call it new verification page and a div new verification. So once I save this, there we go, we no longer have an error and if I try again from mail here, there we go, you can see how it redirects me to new verification and inside of my URL I have a matching token which I can find in my database. Great, so now what we have to do is we have to build the new verification form.
So new verification form like this. If I save of course I'm going to get an error because that does not exist. So let's go inside of components, auth and create a new file new verification-form.tsx let's mark this as use client and let's export const new verification form and let's return a div new verification form. Then we can go back to page.tsx and we can import that from components auth new verification form. And there we go.
We no longer have any errors and now we are free to develop directly in the new verification form. So first things first, let's go ahead and let's import CardWrapper from ./.cardwrapper or components.auth.cardwrapper. And let's go ahead and replace this div with the CardWrapper. And let's go ahead and give it a header label of confirming your verification and let's give it a back button href of slash out slash login and let's give it a back button label of back to login like this. Great!
And now what I want to do inside is add a div here with a class name of flex-item-center with full and justify center like this. And inside what we are going to do is we're going to add a little loader so for that we have to install a package called react spinners so I'm going to shut down my prisma studio and do npm install react spinners like this So make sure you add this package and then we can import any loader you like so you can Google react spinners to see what kind of spinners they all have but I really like the one called beat loader here. So in here you can now render the beat loader like this and there we go you can see how this is going to look like so when a visit so when a user clicks from their email here is gonna start loading and automatically verifying this URL link using the token which we have inside of our URL. Just a quick tip, if you don't have the token inside of your URL, confirm inside of your LibMail in the send verification email that you have a token as the parameter that you properly assign it to the URL and then find where you use the send verification email.
So we use it inside of login and register. So in here, ensure that your first parameter is the email. The second parameter is the token. And the same thing in the login function. The first parameter is the email and the second parameter is the token.
Great, so let's go back to the new verification form which we are working in. It is located in components out new verification form here. What I wanna do now is I want to import useSearchParams from next navigation and let's go ahead and add them here so search params from use search params of course make sure that you've marked this as use client so you can use hooks inside and then let's get the token using search params dot get token so how do I know that I need to get the token exactly because of my URL so you can see that this token value is stored inside of a token query right here so that's how I can get my token great so what we have to do now is we have to write an onSubmit function and we're actually gonna wrap this inside of a useCallback because we're gonna put it inside of a useEffect. So let's go ahead and import useCallback from React and let's replace this onSubmit to useCallback add an empty dependency array. And in here for now, we can simply console log the token and add a token to the dependency array.
Now let's import UseEffect from React and let's call UseEffect here. And inside of UseEffect we are simply going to call onSubmit once like that. And now if you go ahead and open your console log you can see the token which is stored inside of my URL being logged here and you're probably noticing that it's being logged two times. You don't have to worry about that. That is only because of I believe it's called React strict mode, which in development calls every use effect twice.
So in reality, in production, this is only gonna be called once, right? And I believe we might even get some bug here because in this onSubmit, we're gonna check if the token has expired. But once we confirm it the first time, it's gonna be expired. So then when it triggers another time, we're gonna get a little error here, but that's only gonna happen in development, not in production. Because in development, react calls useEffect twice.
So I just want to bring that to your attention so if you're seeing it twice that's completely fine I'd have that as well. Great so now what we have to do is we have to actually create a functionality to call the verification. So we're gonna call that new verification action so let's go inside of actions and create a new file new verification.ts let's go ahead and mark this as use server at the top here and let's import the database from s-lib database let's import get user by email from data user and let's import get verification token, not by token because, sorry, get verification token by token like this from data verification token and I'm just noticing now that I have a little misspell here so let me go ahead and quickly resolve that so you can check if you have that as well. So verification is spelled V-E-R-I-F-I and I think inside of my data folder I misspelled it V-E-R-F-I so I have to rename this to verification by adding a little I here. I just don't like typos in my code.
And now I have to look throughout my project and see where I am misspelling it. So let's see, I believe this is just some cache. This should be completely fine. So when you rename your file you have to go through all of your other files to see if they are still using it properly. So I'm going to search for get verification token here.
All right so I use it inside of verification token data. All right I use it inside of tokens here. There we go, you can see how I have an error here because I renamed my imports. So go inside of lib tokens and in here I have to rename this to be Verification. There we go.
So we resolve that. Now let me refresh this page. All right, I think that's the only place where we have it. Get verification token is inside of tokens. And here where we are working in the new action.
So actions, new verification. I have to rename this as well to verification token. There we go so no more errors inside of my code. Great so inside of here let's export const new verification. Let's make it an asynchronous function which simply accepts the token which is a string.
Let's get the existing token using await get verification token by token and passing token. If there is no existing token in the database we are going to return an error saying token does not exist meaning there is no way we can verify your email. And now let's check if token has expired by using new date existing token dot expires like that is smaller than new date as of now. And if it has expired, in that case, let's go ahead and return an error, token has expired, like this. And then let's go ahead and find the user we are supposed to validate using this token.
So await getUserByEmail is going to be existingToken.email. If there is no existing user in our database connected to that email, that could mean that the user changed their email in the meantime somehow. So let's just return an error. User does not exist. Or maybe more specifically, email does not exist in our database.
And if all of those tests above have passed we can finally do await database.user.update and in here let's use where id is matching to existing user.id and the data we are going to update is that email verified is new date but also one more thing that we are going to update is the email to be existing token dot email. So why am I doing this as well? Why do we need this update? Well, during the registration process, it is not needed, but we are going to reuse this new verification server action for whenever user wants to modify their email, right? So when we create the settings page and the user adds a new email, We are not going to immediately update their email in the database.
We are simply going to create a token with that new email and send an email to that email. And then when they confirm it we are going to update the the email value inside of their database. So that's why we need to do this as well. So in registration process this is gonna be exactly the same as it was before. So that's why maybe this isn't making sense to you.
But keep in mind that we're going to reuse that for when user wants to change their email. So that's why we have to update the email here as well. Great! And then finally we can go ahead and remove the verification token. So verification token dot delete where id is existing token dot id.
As simple as that. And let's return success email verified. There we go. So we have our server action finished. Now we can go back inside of the new verification form inside of our components auth, new verification form right here.
And let's go ahead and let's import that action. So import new verification from actions new verification here. And Let's go ahead and call the new verification here on submit. So I'm gonna do a new verification, pass in the token, which we have inside of here like that. And of course we can go ahead and check here immediately if we have the token or not.
So if we don't have the token, we can break the function immediately like this. Well, that's actually not what we're gonna do. What we're gonna do is we're gonna throw an error, right? So let's do that. So let's import useState here.
And let's go ahead and let's define error to be setError from useState and let's give it a value of string or undefined and let's do the same thing for a success message so set success like that so in here if we don't have a token and we try to submit we're simply gonna do set error missing token and we're going to break the function then like that and then instead of new verification let's do .then and let's get the data and in here let's set success to be data.success and let's set error to be data.error like that and let's also add one.catch in case something breaks and let's manually do setError to be something went wrong like this And what we have to do now is actually render the errors. So let's import our form error from ./.formerror or components I think it's just components like that. And let's do the same thing for form success from form success. Great. And now let's go ahead and render those so here at the bottom below the beat loader let's add form success to have a message of success and form error to have a message of error.
And there we go, you can see how it says the token has expired, right? So you should get this message if you use the old verification token you can see I sent this 17 hours ago so obviously it has expired by now but you can see how I'm still showing the loading indicator even though I got an error and here's another thing you can test. Inside of your URL remove the token completely and now you're gonna get an error missing token right so choose one error at least for you to have here and let's go ahead and modify this now so we are only gonna show the bit loader if there is no success and if there is no error. So if it's a completely empty slate. There we go.
So you can see how now the loader no longer shows but if I refresh it will only show during the actual server action call that we are doing right here, new verification. So on the use effect, the moment the component mounts, we call on submit, which then in return calls the new verification if it has a token inside of search params here. Great. And now what I want to do is I want to also modify this form errors and form success, but let's go ahead and try and create an actual new token. So for that, I'm gonna go ahead and go inside of my Prisma Studio, and this is the email I can send my emails to.
So confirm inside of your resend that you're using the email where you can send emails to. And let me just close everything I don't need here. So go inside of your app and try to log in again, right? And this should send you a new email or you can clear database and create a new account with this email and there we go confirmation email sent so now I should be getting a new email there we go so when I click here let's see if that's going to work email verified and then we have an error token does not exist right so that's the thing that I was telling you about that's because our use effect fires twice right so on the second iteration it's looking at an already confirmed token which we removed from the database. But still, I kind of want to cover this case just in case it can happen in production, even though I don't think this can happen in production because it won't fire twice, but we're gonna do our best to cover that as well.
And here's a fun thing. Let me just check if I'm now, if I am now running my Prisma Studio, I think I shut it down. So let me open it again, NPX Prisma Studio. So in here, if I look at my user and let's find it by this email so tutorial mailing and look at this we have officially verified our email which means that if I try and log in again I will be allowed to log in let's see and there we go I am officially confirmed and I can log in inside of my account. Perfect.
So let's just try one more time to resolve that little bug that we have. So I'm gonna do the following. I'm gonna go inside of my Prisma Studio. And let me just remove all the other users. So I just want to focus on the one where I can send my emails to, this one.
And what I'm going to do, let me just refresh this again. There we go. So what I'm going to do is I'm going to remove my email verified property like this, leave it at null and click Save Change or simply remove it completely and just you know register again if that's easier and now if I go ahead and try and log in again I will be sent a new verification email But before we click on it and verify our email again, let's try and protect our little app here from that happening again. So the first thing I'm going to do is go inside of the onSubmit here. And what I'm going to do is if we already have a success message or if we already have an error message simply break the function right so in then we also have to add those to the dependency array so let's add success and error here and let's go ahead inside of here and let's only show the error if we never had success in the first place like this and I think that this should improve the behavior a bit So I cannot log in but I got a new email here.
There we go. Confirm your email. And let's see if this will be stuck at email. Confirmed verified. Oh, so it's still showing token does not exist.
But as I said, yeah, I think there's kind of no way around this at the moment. You can play around if you want to, but as I'm saying, this only happens because you're calling useEffect twice, right? And I believe that it won't even listen to this logic because it's in some special react state where it's being called twice with the exact same values. I don't think it even considers this new values being changed. So I think that's why this is happening.
But as I said in production we're gonna test it of course later. I think this will not happen at all and you're just gonna see a message saying email verified. So if I try this again, what was my email? Tutorial mailing gmail. And if I log in again, I should be able to log in.
There we go. Perfect. So we successfully verified our email. If I refresh here, I'm going to get email verified to the new date. Perfect.
So let's just recap our entire flow. When the user registers using the register server action, we generate a new verification token using the email they used to register. Then we send the email to that, we send the verification token to that email. Inside of the verification, Inside of the email, we are using a route called Auth New Verification, which we added to the public routes. Then inside of our app, Auth, we have that new verification page, which renders the new verification form, located in components, Auth verification form.
Inside of here, we use the search params to search for the token, which was appended inside of our mail function, send verification email. So the second argument was token, and in here we append that token. So we check if we can find that token in the URL. If on the onSubmit, which fires automatically in the use effect, the moment the component loads, is missing, we return an error, token is missing. Otherwise, we call the newVerificationServerAction, or, you know, in your case, this can be an API route as well it really doesn't matter.
Instead of new verification we send that token from the URL. We search the token using the function get verification token by token which we defined in here so we use the find unique where token matches the token. If we cannot find the token in our database we return an error token does not exist. Second thing we do is we check if an hour has passed from the creation of the token. If an hour has passed we return token has expired.
Otherwise if it has not expired we check for the user which this token was intended for using the email so get user by email. If the user doesn't exist which you know it can happen there are edge cases In that case we throw an error the email for this token which you're trying to verify does not exist. There's nothing we can do here. Otherwise if we can find the user we update the user using that user's ID. We change the email verified to be the new date meaning today I verified this and we also modify the email to be whatever was stored in the verification token because remember this exact logic is going to be later reused when we add the settings page to change to a new email so That's why we also have to update email in here and not in the settings because we don't want to immediately change the user's email.
Otherwise they could use anyone's email for that, right? That's not good. We have to ensure that they can verify the email that they are trying to change. And last thing we do is remove the old verification token. No need to store that inside of our database anymore.
And we return a success message email verified. And all of that is reflected inside of this dot then. We also have an additional catch in case something goes wrong which is out of our control here and we have a little loader here. We have a success and we have an error. And yeah, we have that little bug, which as I say, I'm pretty confident it only happens in development because the React strict mode, you can see that it's completely ignoring the fact that we already set a success message there but yeah I tried to fix it but you can see that it's not working we're gonna test that again in production as well.
Great great job! What we're gonna do next is a functionality to reset our password So we currently don't have that little question here. So we're going to add a little question, forgot your password. And then we're going to do a very similar logic. We're going to send user an email and we're going to allow them to add a new password if they have the correct token inside of their URL.