All right, so now let's go ahead and let's create the verification process for credential registration. So the first thing we have to do is we have to go inside of our Prisma schema and we have to create a model which will be used to verify our credentials, right? So let's go below the account here and let's create a new model called verification token, like this. Let's go ahead and give it an ID, which is a type of string. It is the default ID and the default value of CUID.
And let's give it an email of string and let's give it a token of string and let's make sure that the token is unique. And let's give it an expires of date time. So we're gonna make sure that the verification email can expire, right? We don't want anyone else to abuse that. And let's add a unique rule for the combination of email and token.
So only one unique token per specific email. Great, so now we have the verification token here and there's no need to create a relation with the user. It can live on its own like this. And let's go ahead and push that where we need to push it. So what I recommend you do is that you shut down your app and run nbx prisma generate and then let's do nbx prisma database push like this.
So now you should have a new collection in your neon database and I believe you can also check that immediately here in NBX Prisma Studio. So if I run this right here and if I go inside of here you should see a new collection verification token as I have right here so everything is fine great so now let's go ahead and let's create a couple of functions which we are going to use to create this so the first thing I want to do is I want to create my data lib for the verification token. So let's create a verification token here.ts so the same way we did like with user and let's import the database from add slash lib database and let's write export const get verification token by email to be an asynchronous function which accepts the token which is a type of string and inside we are simply gonna open a try and catch block. In the catch, we're gonna return null. Otherwise, let's get the verification token by using await database.verificationToken.findUnique.
Let me see if I can expand this. There we go. Find unique where we have a matching token like this. And then return the verification token. And my apologies, this one should be email, right?
So this is get verification token by email so we are not using the token to search we are using the email to search so where email like this and in this case it's not going to be find unique it's going to be fine first like this so email email like that great And let's go ahead and copy and paste this. And let's name this one get verification token by token. And let's accept the token prop and then here we can use find unique and we can use the token query. There we go. So make sure you have get verification token by the token field and get verification token by email.
In here we use find first and we query by email so make sure you have both of those. Now that we have this let's go ahead and create a lib which is gonna be used to generate these tokens and make sure that you know if an existing token exists it gets removed So let's go inside of lib and create a new file called tokens.ts. So in here I'm going to keep all kinds of tokens which we are going to generate. But the first one is going to be export const generate verification token which is going to be an asynchronous function which accepts the email. And then inside let's go ahead and we have to generate the actual token and for that I'm going to use the UUID so I ensure that it is always unique and for that we have to install and install UUID like that and let's see if we need the types for that so if I import v4 as UUID from UUID there we go I need the types for that So I need to run this command right here.
So npm install dash dash save dash dev or just dash capital D types slash UUID. So we need that as well and that should get rid of this type script error. There we go. So import v4 from here and now we can get our token by simply using, sorry let's import it as UUID v4 like this. UUID version 4.
Great! Now let's go ahead and write const expires to be new date new date again dot get time sorry plus 3600 times a thousand Like that. So basically we're going to expire the token in one hour, right? This is going to calculate the number of milliseconds in one hour. And this gets the current time so we add one hour from now and we wrap all of that in a new date.
So we have that and now what we have to do is check if we have an existing token already sent for this email. In that case let's go ahead and do const existing token to be await get verification token by email because that's the only thing that we have at this point inside of this prop so we're going to use the email one make sure that you have it in here make sure that you use find first and the email query here and if we have existing token in that case we're going to go ahead and remove it from our database so let's go ahead and import our database from add slash lib slash database so in here let's write await database dot verification token dot delete and where is going to be ID of existing token dot ID like that and now we can generate a new verification token so const verification token is going to be await database dot verification token dot create like that and data is going to be email token and expires. There we go and now you can just go ahead and return the verification token like this. Perfect!
So now we have to find a place where we're going to run this function generate verification token. The first place we have to send it, we have to use it, is inside of the registration itself, right? So let's go inside of actions, register right here and we already have a little to do here, great. So let's go ahead and let's import generate verification token from add slash lib tokens right here. Then we're going to go here at the bottom once we create the user and let's just do const verification token.
So verification token is going to be await generate verification token and we're going to pass in the email that the user is trying to create an account with like that. And then we can change this message to be confirmation email sent. So obviously we're still not sending the email, but for now we're just gonna have the verification token here. So let's go ahead and make sure that our app is running and you can also prepare NPX Prisma Studio in another terminal so that you can see when it gets added. So let's go ahead and check it out.
So I'm gonna refresh this right here. I'm gonna pair my Prisma Studio here. And right now I have zero verification tokens here. So nothing in my Prisma database. So I'm gonna create a new, actually this is my email, right?
So this is verification test. It really doesn't matter what's the password. And all this is sent now is tell me that the confirmation email has been sent and inside of my Prisma Studio if I refresh this I should have a new verification token and there we go you can see that we have the exact email which this verification token is used for and we have the unique token using UUID and we have the date when it expires. Perfect! So what we have to do now is actually send an email but there is one place where I want to generate this token before we do that.
And that is in the login. So this user should not be allowed to log in at the moment. If I use that email, which I have right here, And if I try and log in here, it's going to work, right? Well, first thing we have to do is we have to not allow the user to sign in if they still haven't set up their email verification. And otherwise, if they try, we need to send them a message like hey we sent you an email again here on the login form so if they go away from the register form they need a way to send that email again and they can do that very simply by trying to log in and we're just gonna send them an email again.
So let's go ahead and we're gonna do the exact same thing here. So let's go inside of actions, login in here and let's go ahead and import generate verification token from ad slash lib tokens right here. And now what I wanna do in here, when we get the email and the password, let's go ahead and let's try and get an existing user from our database. So I'm going to use await get user by email. So make sure that you import get user by email by data user.
We already have that and it's very simple. We also have the user ID. So in here, we're going to attempt to fetch the user that the current user is trying to log into with the email. And in here already we can check if there is no user or if there is no user.email sorry, existing user or if there is no existing user.email or if there is no existing user.password meaning that they shouldn't be logging in with their credentials instead they should log in using OAuth in that case we can return an error invalid credentials or you can return something a bit more specific like invalid yeah I don't know what will tell the user so it's either gonna be you know user does not exist or yeah let's do that email does not exist let's do that all right And now let's go ahead and check if the user exists but it doesn't have its email verified. So if exclamation point right here, user doesn't have the email verified, existing user, my apologies, existing user does not have the email verified.
In that case, we're gonna generate a new verification token right here using await, generate the verification token and passing the user.email, existing user.email so like this. And then we're going to return success, confirmation email sent. Right, so we're gonna break the function here and we are not even gonna attempt to sign in. But remember, we are not done yet because users can still use the API to log in inside of our app. So we have to protect them inside of the sign in callback.
So we have to do that as well. But we're gonna do that in a moment. I just wanna test out that the new verification token is being generated if I'm trying to log in with the user that doesn't have their email verified meaning that they just signed up for their account or they waited longer than an hour so the email that we send them during the registration is no longer valid so they need a new email so one of those. So let's go ahead and try that out but just before we try it out I think we have to revisit our components auth login form because in here I believe I commented out yeah so I wrote 2FA yeah it's actually already so make sure that you add inside of the on submit function inside of out components out login form just bring back set success data question mark dot success like this. Let's just confirm that we actually show the success message we do.
Alright so let's try it again So this time I'm gonna go ahead and log in again as this user and I just want you to kind of pay attention to the current token, right? Kind of try and remember like the last few characters because the only thing we're gonna notice is that this one this token will be updated and the ID is going to change. So try to keep an eye on that. So I'm going to try and log in now and I should be blocked from logging in and I should get the message confirmation email sent. So if I refresh this, there we go you can see how the token changed and the ID changed meaning that we successfully created a new expiration.
Well we could have just looked at the expiration yeah. We just created a new expiration for the token. Great! So we're almost there. We just have to do the same thing now inside of our auth.ts because you already saw that sometimes Auth can redirect to that special page which it generates by itself.
So from there we don't protect anything, right? So that's why whatever you do in your login or register functions you also have to do an equivalent inside of callbacks. Because what you do in the register and login actions or API routes, it doesn't matter. You're doing that, well, yeah, for security, but also for user experience. But for total security, you also need to do it inside of NextOut.
So you need to have equivalent things inside of next out. I could have easily done this you know inside of registration as well but it's easier to do it in this way. Like I like to my I want I want I like to know that my out is taking care of this. So you know When it comes to out you never want to be worried that you're missing out on something and this is the best way to do it right here. So let's go ahead and we have to create a callback called the sign in.
So I'm gonna do this at the top here so a synchronous function sign in like this and inside we're going to structure the user and the account by default let's return true meaning we are allowing you to sign in. But here's what we're gonna do. We're gonna check and we're going to allow OAuth without email verification. So the way we can do that is simply by checking if account?provider is not credentials. Make sure you don't misspell this.
So credentials, in that case, simply return true. That's it. So If it's anything other than credentials, I'm gonna allow this without email verification. Of course, when you start adding more providers in your code, you might have to adjust this. I'm not sure what kind of providers exist in NextOut.
So if you might want to be more specific, for example, you can do the opposite thing. You can do if account.provider is credentials. And in that case, you know, open the whole logic to check whether the email is verified or not. But I'm doing it this way. It's simpler for me, right?
And inside of here, I'm gonna attempt to get the existing user using await get user by ID using user.id inside of here. And I'm gonna simply check if not, So put an exclamation point, existing user question mark dot email verified. In that case, return false. So very simple. If the email, if the user has not verified their email, I'm gonna block them from logging in.
That's it for now. And I'm gonna add a to do comment here, add to FA check. So we're going to have that later. Great. And if you want to, you can add a comment here, prevent sign in without email verification.
Right. So as I said before, you know, if you add more providers in your code, you have to check if this is still okay, because I don't know what kind of providers exist. So right now I'm only requiring email verification on the credential provider, right? And I'm not requiring Google or GitHub. So if you're using this in production and you add some new provider, make a decision if you still want only the credentials to be checked for email verification.
If not, you can go ahead and modify this logic to do the opposite. So you can do if account provider is equal to credentials in that case you can go ahead and do this thing for example something like that. But as I said, I know what is the structure of my app, I know which providers I use, so I want to allow everything to simply be able to log in, except if you're using the credentials, then I'm going to do a check on you to see if you have your email verified. So this is what should happen now. So go inside of your actions login right here and for now I'm going to comment this out right so I'm not going to generate a new token.
So imagine that I didn't write this code where we block the user from our login function. So this is what's gonna confirm to us. Now it should throw an error instead of this because we just modify that inside. There we go. You can see how it threw an error.
That's exactly what we want. So even without this code in the login function we have a fallback inside of Auth which will never allow that kind of user to log in if they don't have their email verification. So that's why it's important to match the logic which you write inside of your, you know, be that a server action or an API route, match that inside of callbacks as much as you can, it's going to highly improve the security of your application. Great. So now that we have all those systems in place, and of course, make sure that in the login, you actually bring this back, which if you commented it out with me.
And what we have to do now is we have to set up our mail provider. So for that I'm going to be using Resend because it's extremely simple to set up. It has a very generous free tier and it will take us five minutes to do this for this tutorial. I'm gonna give you some alternatives as well if you want to for any reason, but most of them are more expensive to start with and require a credit card or something. But Resend doesn't require anything.
You can just start using it and you can finish the entire tutorial just by using Resend. Alright so let's go ahead and let's do that. So head to resend.com or use the email in the description and go ahead and create an account. If this is your first time you're gonna get prompted to create a name so I'm gonna call this Alpatorial and create a team. And there we go.
Now let's go through this onboarding. So first let's add an API key. Like that. Great. And what's great is you can already test whether it's working or not.
So you should have, it should have the email you logged in with right here. You can see I created a new email just to test this out. And just go ahead and click send email. And if I check my inbox here very quickly it should appear. There we go.
Can I hide this sidebar? I can't. Okay. There we go. Congrats on sending your first email from onboarding.
Great, so now let's go ahead and let's go inside the docs right here. And Let's use the Next.js quick start. And first they tell us to create an API key. So we already have that. So I'm keeping that right here.
Don't close this tab yet. And now let's do npm install re-send. So I'm gonna copy this. Let's go inside of our terminal here. I will close the Prisma Studio and I'm gonna run npm install resend like that.
Let's see what is the next step. So we don't need any email templates here. I just wanna see if, Okay, so they use the Resend API key. So that's what I was looking for, the name for what to save this in. So let's go ahead inside of our environment variables after this has installed.
And let's simply add the Resend API key and copy the API key from your own boarding right here. There we go. Great. So now that we have the recent API key, I just kind of have to explain one thing about the recent. So you can go ahead and find, you know, the free tier.
And if you're interested in that, I'm not sure where it is. Is it settings here? There we go, yeah. So you have 3000 free emails a month, 100 free emails a day in the free tier right here, right? But right now you can only send emails to yourself.
So to this email which you can find in emails. You cannot send it to anyone else until you add your domain. So that's what you have to do. You have to add a DNS record to your domain. And no, you cannot use Vercel or Heroku or something like that.
No, you need to own a domain. So good thing for tutorial, this is more than enough that you can send yourself emails. But if you want to share this with your friends or something and allow these emails to be sent to everyone, the easiest one is to simply add a domain. I'm gonna show you how to do this exactly but in the end of the tutorial after we deploy because this is for production only. You don't need to do this for development.
Great, so just make sure that you have your API key here. Right, if you cannot see it anymore, I don't know if you can see it. Yeah, let me just see, edit API key. Yeah, if you accidentally lose your key, you can always click create a new API key, right? And just give it full access, of course.
And then you can copy it again and just paste it inside of re-send API key. Great. So what I want to create now is my mail library. So let's go ahead and let's go inside of lib and in here create a file mail.ts like this. Let's import resend from resend and let's define resend to be new resend which uses the process.environment.resend underscore API underscore key and confirm that it matches here so resend API key copy it from here paste it here make sure there is no typos And now let's export const send verification email here to be an asynchronous function which has the email which is a string and token which is a string.
Right, or I'm just gonna collapse this like that so we have the email and the token and then inside of here let's generate the reset link sorry the confirm link is gonna be HTTP localhost 3000 slash auth slash new dash verification and then we're gonna add a question mark and we're going to add a param for the token so let me just zoom out so you can see this in one line and we're gonna add that token at the end. So this is the important part. Well everything is but make sure it goes to slash auth slash new dash verification and make sure that you add a question mark and the param or a query token and then stringify the token which we are passing from the props here. So this is the confirmation link which we are going to email to the user and then we're going to create this new verification page and we're going to use it to detect whether the token has expired, whether it exists, and if everything is okay, we are simply going to change the user's email verified status in the database to a new date. And of course, we're going to have to change this later to something dynamic so it works in production, Otherwise, you know, your users are gonna get local host emails, which won't make sense.
But for now, this is good enough. And now what we have to do is send the email. And now you're gonna see how incredibly simple that is with resend. So just resend.emails.send, from, and in here, choose onboarding at resend.dev, like this, to email subject is gonna be confirm your email. And we're gonna have HTML, open backticks and you can just write HTML here.
So write a paragraph like this and I'm gonna write click, open an anchor tag. I forgot how to write anchor tags, okay. In here, I'm gonna write here and let's give this anchor tag an href to the confirm link. And in here, you can simply write to confirm email. There we go that's it that's all the code we need so make sure that you have a little paragraph here right which starts here and ends here Then open an anchor tag which has an href, open annotations and add a confirmation link here.
And now let's go ahead and use this send verification email inside of actions, register here. Whoa, how did they get there? There we go. We have this to do right here. So let's go ahead and import that from our mail.
So send verification email from at slash lib slash mail like this. And let's go ahead and remove the to do and use await send verification email. And we expect two arguments, email and then a token. So first let's pass in the email and then actually we can do this. We can use it from the verification token to ensure that it's the one that's in the database.
So let's use verification token with email and verification token dot actual token. Now let's try that out. So I'm gonna go ahead and I'm gonna create a new account here. So I'm gonna call this verification verification at mail. Actually yeah we need to use the actual you need to use your email.
So make sure that you use the email where you can send the email address to. So for me, that's this one, tutorial mailing. It should be written right here below, or you can click on emails. So make sure you do it with this email because no other emails will work until you add a domain. So I'm just gonna go ahead and copy this.
Copy. Right here. If you have an account already, just remove them from your database. So there we go. Add an email that you can find here in resend.
Let's go ahead and create an account and now I should be actually sending an email. So let's see if that's working. Confirmation email sent and there we go. Confirm your email and if I click here it should lead me it should redirect me back to login because I'm not logged in and we didn't add this new route which is the Auth new verification token we did not add that to our routes but it is officially working. Great!
So now what I want to do is also send the verification email in the login page, sorry in the login functionality. So let's go ahead and do that as well. So I'm gonna go inside of actions, log in here. And now let's go ahead and import the same thing. So I'm gonna import send verification email from s-lib mail.
And in here, let's await send verification email. First argument is the email and the second argument is the token itself like that so now if I go ahead and log in using verification, no, sorry, using the, my email here, which is tutorial mailing. All right. So I created an account, right? But I didn't verify my profile.
So if I try to log in now, I should be blocked from logging in and instead a new email should be sent so let's try that out and there we go you can see how now I have two emails this is the old one and this is the new one a minute ago and I can go ahead and click here again. Perfect, so we can officially send emails in our app. Great, so I'm gonna end the module here and the next one we're gonna create that new verification screen where we're actually gonna verify the email. For now, if you want to, if you have a domain and you know how to add an DNS record, you can do that right now. If not, wait till the end of the tutorial when we get to deployment and I'm going to show you how to do that.
Great, great job!