In this chapter, we're going to focus on adding authentication to our project. We're going to do that by implementing BetterAuth, a new auth library that is slowly becoming the standard for auth everywhere. So let's go ahead and start by following the BetterAuth documentation. You can use the link on the screen to visit their page. Let's go ahead and click get started right here and let's go ahead and click on installation.
I'm going to select my package manager which is npm and I'm simply going to run npm install betteralph. Let me go ahead and just make sure that I'm not running my app at the moment and I don't need Prisma Studio either so I have no servers running just npm install betteralph And immediately I'm going to show you what version of BetterAuth I'm using since it is a pretty important package here. So as you can see I'm using BetterAuth 1.3.26. So as always my advice is the same as for TRPC. If you are on the same major version as me you're probably not going to have any problems.
But if you're watching this from version 5 of BetterOut you will most likely have some problems and you should probably use the same version as me and then upgrade later. But given what I've experienced with BetterOut so far is that they take extreme care when it comes to good developer experience. So I have no doubt that they will take care of all kinds of backwards compatibility. But still, I'm showing you my version just so you're aware on what version I am building this authentication module. Now let's go ahead and let's set up better out secret and they've generated this handy button to generate secrets for us.
So let's go ahead and go inside of dot environment here. Let's add better out and let's add better out secret. I have a preference of adding these in quotes like that. Let's go ahead and see what else we have to do. We have to set the base URL And we can actually copy this as it is because it is HTTP localhost 3000.
If you're not sure what is your app URL, go ahead and run npm rundev and in here you will see local HTTP localhost 3000. Great. Now that we have that, let's go ahead and create a file named auth.ts inside of the lib folder. So I'm going to go inside of source, lib, and I'm going to create auth.ts. In here I'm going to import better auth from better auth and then export const auth better auth and let's leave it empty as in the example above.
In here we are told to configure a database here. So let's go ahead and look just below where it says alternatively if you prefer to use an ORM, which is our case, We use Prisma. Let's click on the Prisma tab and let's see how to configure it. So first things first, we have to import the Prisma adapter. So import Prisma adapter from weather-alt, adapters, Prisma.
Then let's go ahead and import Prisma or is it database? My apologies I'm too zoomed in so I can't see where my imports are coming from. I think it's Prisma from forward slash DB or if you prefer you can use it like this so you know exactly where it's coming from. So once you have that, let's go ahead and let's do database, Prisma adapter, pass in Prisma, open an object, provider, Fastgres, because that's the database that we are using for Prisma. So we import BetterAuth from BetterAuth package, we import Prisma adapter from BetterAuth adapters Prisma, and Prisma from lib database, which is this little util we've created in the previous chapters.
What we have to do now is we have to create database tables. What I would suggest you do right now is you go ahead let me just close this you go inside of source... My apologies inside of Prisma, schema.prisma right here and you can go ahead and copy this entire file just in case. You actually don't have to worry because if you did git workflow the same as me you can easily revert your changes. But let's go ahead and let's run npx better-out forward slash cli generate.
So I'm going to go ahead and run this inside of the root of my application. It's going to recognize Prisma so let me just confirm this update here and now you can see exact CLI version that I'm using in case you want to use the same version. So it noticed Prisma schema and it tells me that it will overwrite it because it will write directly into that file. Let's go ahead and confirm that because we don't really have anything important there but that's why I told you to copy the file just in case you had some important things I don't know how exactly you're following this tutorial. But nevertheless, in my experience, it never actually overwrites the entire thing.
Yeah, as you can see, I still have something here and I think we actually might have a problem here. It's because we already had the user object. Yeah, let's go ahead and do this. Let's remove the entire thing. Let's just leave it empty.
Schema Prisma like this. And let's try again. Yes. And now you can see the user model is correct. The reason we had a problem is because we had an existing schema Prisma and that schema Prisma already had the model user.
Because if you remember, when we followed a Prisma guide, we copied this model right here of the user. So it ended up having a problem, right? That's why I just deleted all models now and then I rerun the generate command. It found the Prisma schema again and then it populated it correctly. So let's go ahead and see what fields we are supposed to have inside.
So you can see whether you have the correct ones too. We should have the user with an ID, name, email, email verified, image, timestamps, session, accounts, unique field for the email and map to user. Then you have the session which has all of these fields and the relation to the user as well as these unique and map fields. Account and verification. These are the fields you're supposed to have in your Prisma schema and you should have no errors.
Save that file now and let's go ahead and migrate it. So npx prisma migrate dev like that And let's go ahead and call this better out once we get the prompt. Keep in mind that if you have existing data inside, you will most likely have to delete that data. You can do that by using npx prisma migrate reset. This way you will delete all of your data.
For example, you can see the warning I'm getting here. You're about to drop the users table. So yeah, in my case, that's completely fine. Better out fields. There we go.
So now we have a new Prisma schema. And if you run npx Prisma Studio, you should now no longer have any users in here, but you should now have an account model, session model, user model, and verification model. All of these fields are needed to properly run better out. Perfect. So once we have done that migration, I think that now we can do npm run dev.
And let's see what we actually have to do now. So we didn't run the migrate here because that's only for specific adapters, not for Prisma, but the Prisma migration script is very easy anyway. So that's actually it when it comes to what we have to do. So for example, let's now enable email and password login. We do that so simply by going inside of source lib alf.ts email and password enabled true.
That's it. That's how you enable email and password login in BetterAuth. And now let's go ahead and let's build a form that will accept those fields. So I'm gonna go ahead and I'm gonna go inside of source, app, and let me create an auth route group and let me create login. And inside of here, page.tsx.
Const page return login export default page. So what I've done is I've created a reserved file name page with a default export and I've put it inside of a login folder, inside of the app folder. And in between those two, I've created an out folder in parentheses. What this is used for is to organize your folders, your routes, your groups, right, Without affecting the URL. So the way you can access the login page now is by going to localhost 3000 and then forward slash login.
And then you can see the text login here. So notice how, let me just show you here, HTTP localhost 3000 slash login. So that's my current URL. So notice how I didn't have to write out inside. That's what this is used for because usually inside of the app folder any encapsulating folder name which holds a reserved file name page becomes part of the url.
Well if you want to avoid that and you just want to use folders for organization purposes like I am now for out you can add parentheses and then it will not be a part of the URL you will just simply go to forward slash login. Perfect. So now that we are inside of the login here let's go ahead and build a form. So the place where I'm going to do that is going to be inside of source. I'm going to create a new folder called features and inside of here I'm going to create an ALF feature and then in here I'm going to create components let's go ahead and create a login form.tsx let's mark it as a use client and let's go ahead and add all the imports that we are going to need.
So the first imports that we're going to add are going to be ZodResolver from HookFormResolvers.zod, image from nextImage, link from nextLink, useRouter from nextNavigation, useForm from React HookForm, toast from Sonar, z from Zod and button from Components UIButton. If you're wondering where do all of these imports come from, they all come from the chat-cn-ui command that we run in the first chapter. So if you remember, we run npx chat-cn add dash dash all. What that did amongst adding all components was also add a bunch of package.json files. My apologies, a bunch of packages in the package.json to make all of those components work.
So yes, you already have, for example, react day picker. You already have where is my react hook form. Here it is react hook form. You already have Zod. You already have hook form resolvers.
So that's where all of these imports are coming from. All right. Now let's go ahead and import everything we need design wise. So besides the button, we're also going to need every component from components UI card. And then we're going to need everything from the form field.
So form, form control, form field, form item, form label and form message. And last three imports are going to be an input. And we're going to need out client. Let's comment this out for now because we are getting an error. We will come back to this later.
I think I forgot to do that. Yes, it's a super simple thing but we can continue with what we're doing right now so we don't lose focus. So let's start by defining the form schema. So this form schema which is a login form is going to have two fields, email and password. So let's define that using Zod.
So login schema is equal to Zod.object which accepts email which is a type of z.email and password which is a type of z.string with a minimum value of 1 as well as some error messages if that rules are broken. Now while we are here let's quickly define the type for this form. So login form values are using z as zod.info type of login schema. And let's export function login form here. Now let me just fix this.
The props are going to be... Actually, we don't need any props here. We can just go ahead and do this. Now let's define the router to be useRouter and let's define the form to be useForm. So const form useForm.
Now inside of this form let's go ahead and give it all the properties needed to infer the login schema. So use form, open an object, add the resolver, Zod resolver, which uses the login schema object, and default values, which are email and password. And in order to make this type safe, let's also properly infer login form values here. There we go. Now let's go ahead and define the on submit method which is going to be an asynchronous login form values function and let's just console log values.
Let me fix the typo here. Now let's define isBending to be form, form state, and then isSubmitting. This way we store this in a shorter named field. So it's easier to look at our code really. Now let's go ahead inside of here and return a div.
Let's give this div a class name of flex, flex column and gap of 6. Before we do anything further, I want to render this LoginForm component somewhere so that we can actually see what we're doing. Instead of this page, instead of this div, let's do a simple LoginForm. From at features-auth components-loginform. So right now, as you can see, we can't really see much.
But we are going to slowly start to see that now. So inside of here I'm going to add my card element. I'm going to go ahead and go inside of the card and give this a card header and I'm going to give the card header a class name of text center. Inside I'm going to add a card title with a text welcome back. Below the card title I'm going to add card description with a text login to continue.
Outside of the card header I'm going to add card content and then I'm going to add a form element. Instead of this form element I'm going to spread the form constant which comes from the hook which we have defined above. And now inside of this form let's go ahead and let's add a native form element like this. This native form element will have an on submit method of form handle submit and pass in the on submit. Then let's open a div inside of that form with a class name of grid gap6.
Then let's open another div with a class name of flex, flex column and gap4. Then let's add a button. Let's give this button a variant of outline. Let's give it a class name of full width. Let's give it a type of button so it doesn't accidentally trigger the submit method.
And let's disable it if we are pending. And in here let's say continue with github let's go ahead and copy and paste this and let's change this to be continue with Google there we go now outside of this div let's add a new div and let's close it like this with a class name which is going to be grid gap6 Add a form field which is a self-closing tag, give it control property of form.control, name which is email, render. Let me just properly extract from the render field the field property and then inside of here let's go ahead and let's render the form item like this. Inside form label, which will simply say email. And then form control.
Let's add a self-closing input tag, give it a type of email, give it a placeholder of m at example dot com, and let's go ahead and spread the field property like this. And now below the form control let's add form message here which is a self-closing tag. In here we're going to render any errors. Now that we have established this we can go ahead and we can copy this form field, so this self-closing tag, and paste it below and this one will control the password field So change the form label to be password. Change the type to be password.
And for the placeholder, you can just add some asterisks here. Just like that. Now outside of this self-closing form field, add a button with a login text. Give it a type of submit, give it a class name of full width and give it a disabled if is pending. Just like that.
And now, just outside of this div, add a new div with a class name text center and text small Inside of here we're going to ask don't have an account? And then let's add a space like this. If you don't have an account let's use the link to sign up by going to href forward slash sign up and give this a class name underline underline dash offset dash four. If you're getting an error for this asterisk you can just use appos like this. So don't have an account and when you click it will lead you to a 404 page because we didn't develop it yet.
But that's how we're going to switch between login and register. So if you try and hit login now you will just get some errors. But even if you try submitting something the only thing you'll actually be able to see here are the logs because the only thing we added was console log values here. So let me open inspect element here, let me go inside of the console and once I click login there we go. Whoops!
I can only see the email and the password that I have entered. If you see that, it means you've developed everything correctly. So before we go any further, I want to go ahead and copy this entire thing and create a register form. So let's go ahead and copy the login form. Let's paste it in this components folder and rename it register form.
And then go inside of the app folder, go inside of the auth, go ahead and create a new folder called sign up or register, whatever you prefer, and then page.tsx inside. I'm going to simply repeat a div which renders register form. Now we can't really import this right now because we didn't change the name. So we have to go back inside of our FeaturesOutComponents register form and let's go ahead and immediately change the name to register form. So now we are exporting a function called register form which means that now we should be able to import that from features out components register dash form.
There we go. So now when user clicks on sign up here they are no longer getting an error but they are also not seeing anything different from the login form. So just make sure that right now you are on localhost 3000 slash sign up. Make sure you are on this page because we are developing the register form now. Or if you have named this register in that case go to register right.
Just make sure you're looking at the new register form. So in order to see that immediately let's change this from Welcome back to get started. There we go. Now I can see the text which says get started. And instead of login to continue, it's going to be create your account to get started.
Like this. And then we can scroll all the way down to the footer, which asks don't have an account and let's change this to instead ask already have an account. And let's use the href to go to login and the text will be login. And now you should be able to switch between the two. Login, it says welcome back.
Sign up, it says get started. Perfect. Now let's focus exclusively on the login form. So I'm going to start by changing the name of the schema into register schema. And I think that we are using it in this three instances here so I'm going to change all three at the same time using command D.
You can change individually if you want to but yeah here's a little trick if you're using Visual Studio Code. Select the variable and then press command D two more times. So one, two and that will select all three instances and then I can change it to register schema. It's a cool little trick. Now let's go ahead and also add confirm password here which is also going to be a string but we're not going to add any rules to it directly here instead we're going to add a refine method.
Refine will get the data and it will check if data.password is equal to data.confirm password and if it's not in that case the message is going to be passwords don't match. And the place where we are going to show this error is defined in a path, confirmPassword. So we are not going to show that in the password field, we're going to show that in the confirm password field. And I think that you can even choose both of them if you want to and then both fields will have the error. Great.
Now I'm going to change the login form values again 3 instances so I use command D if you are on Windows perhaps it's control D so 1, 2 like this. All 3 are selected and now I can do register form values like this. And let's go ahead and add confirm password here to be an empty string like this perfect we can leave everything as it is And we just have to add one more form field. So let's go ahead and copy it and let's paste it here and change the name here to be confirm password and change it here to be confirm password as well. And let me see if that's the only thing we need.
We can actually do it like this. Confirm password, type password. Yeah, everything here should be the same. And then change the sign, log in to sign up. There we go.
And now if I, for example, type in AntonioMill.com here, and if I type 1, 2, 3, 4, 5, 6, 7, 8, and 1, 2, 3, 4, 5, 6, 7, 8, it works but if I change to nine it doesn't work. Perfect. And let me see there was this weird error let me try again I'm not sure how to reproduce that error but it had some weird error message. Maybe it was the specific hot reload state that I was in. Not sure but I think this should work normally.
All right. Just make sure you have added the default value for it. Excellent. All right. So now we have both a login and a register fields here.
And now what we have to do is we actually have to create an account when we register because we can't even attempt to try to log in if we are not registered. So in order to do that, we have to go back to our BetterAuth library here and let me just scroll up because I've obviously missed something here so I've added out TS here But I didn't add out client that's the S and here it is create client instance Perfect. So react that's the one we are using. Yes we are using Next.js but we are using React for the frontend right. So go inside of lib the same place where we created auth.ds and now add auth-client.ds.
Go ahead and import create auth client from better auth forward slash react. And export const auth client here, create auth client and execute the function so it looks like this right instead of here they say that you need to add a base URL except if you are on the same domain so for us it's actually enough for the snippet to look like this. That's all we really need. And now what we can do is the following. We can now go ahead instead of source, features, out, register form right here.
And instead of just doing console.log for our values we can actually log in so we can do await outClient sign up let me first import outClient from lib outClient like this lib outClient and yes you can now remove the commented out code because that's the exact snippet that we added. .Signup.email like that. Open an object, add name to be values.email. Go ahead and add email to be values.email. Whoops.
Password to be values.password. And callback URL for now can be a forward slash and it's URL like so. On success here, my apologies, open another object. These are now the fetch options. So on success of this fetch, let's do router.push to a forward slash.
On error, let's go ahead and get the error message here. And let's go ahead and do toast.error, context.error.message. The only problem is you can't call toast from Sonar before you add toaster to your layout. So let's quickly do that by going inside of source app folder, layout, and just add toaster from components UI sonar. Just like this.
Now everything will work just fine. And there's another thing you can actually modify here. Let me try and see if it's here or maybe it's if it's in concepts here. Authentication, email and password. Here it is.
So we already did this, right? We enabled email and password. But you can actually do even cooler things here. Let me see. So they have documentation for signing out.
They have email verification here. But what I'm trying to find is a very simple way using BetterAuth that you can automatically sign in. So if I go instead of auth client, my apologies, auth.ts instead of source.lib, email and password, auto sign in set to true. What this will do is it will basically automatically sign in when someone registers. Because usually you register and then you have to log in again.
So by adding this little Boolean you will automatically log in. So now let's go inside of source app page.tsx right here. And let's clean this up. So we use this as an example, right? So we no longer have to do that.
Feel free to remove all of these imports and make this a client component. And in here, let's go ahead and let's just simply check our out state. So let me go ahead and find that API here. So this is server side. And let's see how do we do it using client side here it is client.
So we already established the client instance we already tested this. So here it is Use session from create out client. That's exactly what I want to do. I want to see if we are logged in or if we are not logged in. So let's go ahead and add this and let's import create out client from better out react Or perhaps I think we can just import let me go ahead instead of my out dash client.
Yes, I think we should definitely use this out client here. Out client. Maybe not. Not 100% sure. Let me see.
.UseSession. Here it is. And then we don't have to import this. There we go. So in here, let's see data and let's just do JSON.stringifyData.
Like that. And let's add a button which we can import from components UI button log out and on click out client and let's go ahead and find the logout, the signout like this. There we go. But that's only if data is present. So let's not render the logout button unless we are logged in there we go, just make sure you have added use client to your app folder page.tsx and you can now remove client.tsx, we only used it as an example feel free to remove it, we no longer need it.
Let's go ahead and try some things out now. So I'm gonna go ahead and prepare some things. So first of all make sure you have npm run dev running and then let's also do npx prisma studio here. Right now we should have absolutely no users whatsoever. Let me see what's the problem in here.
I'm not sure. Can I refresh? OK, looks like now it's fine. And now I'm going to go ahead here. And first let's go to localhost 3000 just without anything.
And you should just see no because we are not logged in. And Now if I go to forward slash sign up, because that's where my register form is, I'm going to add AntonioMail.com, 1-2-3-4-5-6-7-8 and 1-2-3-4-5-6-7-8 and I'm going to click sign up like this. And looks like I've got an error. So let's see what the error is about. And I think actually know what the error is.
I think I forgot to add the API out. My apologies. So let's go ahead back instead of getting started installation. I should have just followed this to the end. This is definitely my fault because I get very excited and I want to show you immediately.
So let me go ahead and find further usage for Next.js here because I think we also have to add this to our API list. So let's see. Next.js, here it is. Yes, so mount handler. You have to select Next.js and then you have to add the auth all route right here and then it will work.
So let's go inside of source, app folder, API. Let's go ahead and create auth. Then inside of here go ahead and create a catch all route similar to the TRPC one, but in here we do the spread to catch all and then route.ts inside. And then just go ahead and copy this and paste it. I will show you exactly how it looks like.
So from our lib out file and from better out next js we map the post and the get routes. So actually very similar to what we do in trpc we met the get and the post routes as well. Perfect. So now that you have this, let's go back here and let's sign up. Let's wait a second and there we go.
You can see I have my logged in session right here. And not only do I have this session here but I should also now have my user here in the database. Let's refresh here. There we go. My name, my email, every information is here.
My session, my accounts, everything is here. Perfect. And now I have this logout button and when I click that, there we go. I am now logged out. Perfect.
So now our authentication is officially working. So now you might be wondering, all right, how do I redirect, you know? So for example, let's say inside of our app folder page dot dsx, Let's now convert this back to a normal protected server component. So I only want logged in users to see this. How do we do that?
So the way I like to do this is by going inside of source, lib, and creating auth-utils.ts. Import headers from next headers, import redirect from next navigation, import auth from .slash auth because we are in the lib folder. Let's define require auth function which is going to be an asynchronous method. Let's first get the session by doing await out.api get session and let's pass in the headers using await headers. If there is no session, let's redirect the user to forward slash login otherwise let's just return back the session and then let's go ahead and copy this let's paste it and let's call this require on out so the opposite in this case if the session exists, let's go ahead and return to, well, what will in the future be workflows, right?
Basically, actually, we can now just do this. So if the user tries to visit login page while they are authenticated, we should redirect them back, right? So that's why I have these two handy utils. And the way I do it is very simple. So I directly do my auth inside of the pages that I need to do.
There are several reasons for that. The first reason is I can see exactly which page is protected, which isn't. So if I just do require out here Like that, that's it. This page is now protected. You can see how I was immediately redirected.
Now, of course, the first question is can I use a middleware? And most of you are thinking of the Next.js middleware. You can use it, but only use it for better user experience. Do not use it as a security layer. There have been countless instances of out libraries being broken into using the Next.js middleware.
That is because Next.js middleware should not be used as the outlayer. So, there are many tutorials which teach you how to automatically protect many of your pages using the middleware. And that is great. You can do that. There's nothing wrong with that.
It's for better user experience. But that shouldn't be your last line of defense. That's why we developed the data access layer. That's why we won't directly call Prisma calls within server components. Instead, we're going to do that through TRPC and we're going to develop something in TRPC called protected procedure and that way we're going to have actual security layer here.
So yes, if you want to, you can explore how to add this to the middleware. But I've given up on teaching people that because people think that that's a security layer. It is not. So when I say middleware, I mean very specifically on Next.js middleware, which is actually a different behavior than what you'd normally think a middleware is. For example, PRPC has its own middlewares, but using out in them is completely fine compared to the Next.js middleware which is more of a proxy than a middleware.
So, yes, what I just did in the page.vsx was basically enough to protect this. So if I now go ahead into AntonioMail.com, let's first try 12345, wrong password and click login. Oh, I didn't, I forgot. Instead of our login form, we never actually developed any kind of submit method here. So let's go ahead and do that.
So await out client from lib out client so you can now remove this comment and outline. Out client dot sign in dot email. Go ahead and add email to be values dot email. Password to be values dot password. Callback URL to be a forward slash and then let's open fetch options here on success.
Router dot push forward slash on error get the context toast dot error context dot error dot message. Like that And I think that then we don't need CN so we can remove that. And I think we have the same case in register form. We will have image later but we won't have CN. So you can remove CN from both.
And I think that now, okay, let's try logging in. Now we should get an error here. Invalid email or password. But if I add the correct password, 12345678 and log in, I will get redirected to a protected server component. And if I try to manually go to forward slash login now, I can still access that.
That's something we want to prevent from happening. So let's go inside of source app folder out, log in page.tsx, asynchronous method, await, require unauth, like this. And now if I'm logged in, I'm redirected to the protected server component. And some of you might get the idea, okay, can I just do this in a layout file and this way I can protect multiple routes at once? That is the same situation as the middleware.
You can use it if you want to improve user experience, but you shouldn't use it as your security layer. So basically what is a good security layer? How do you know if you have a good security layer? Imagine all of your auth for things like this breaks. Imagine this here breaks.
Imagine page.tsx out breaks. Imagine your next.js middleware route breaks. Will users, unauthenticated users, be able to get access to your data. If the answer is yes, you have a bad security layer. That's why our main security layer will be inside of trpc.protected procedure.
And everything else that I'm doing right now is purely user experience. Because even if I later on, if I remove this line, require-auth for my server component, and I allow the user to fetch and prefetch, whatever, they are just going to get a bunch of errors. So the only reason I'm doing require-auth here is so I redirect the user so they don't see those errors, so they are on the proper place, the login form. So I'm trying to explain to you that data access layer is the only security layer when it comes to ALF, this ALF that we are doing, that matters. Everything else, think of it as improved user experience.
You know, where to redirect the user so they don't see the errors or broken pages, right? But if you have Auth inside of your data access layer, which is for us, trpc, no data will ever leak to an non-authenticated user. And that way you won't even care if you use the layout or if you use the middleware. Yes, but still, I like to be very explicit with my routes. So I'm gonna go the same thing in the sign up here.
Asynchronous await require an auth. So now if I go to forward slash register or forward slash sign up, my apologies, Same thing, I'm redirected to the protected server component. Amazing. So now let's go ahead and wrap this chapter up by developing one last thing. So let's see what I've prepared here.
So we set up BetterAuth, we added AuthScreens, we added AuthUtils. The only thing we didn't do was add a protected procedure so let's do that now. So let's go ahead and go inside of source, trpc, init.ts and in here we're going to go down here to the base procedure and let's export const protected procedure protected procedure will simply extend the base procedure this is a good practice in case you extend the base procedure itself in the future So you don't have to keep track of, you know, if protected procedure will have everything that you've added to the base procedure. You can just simply extend the base procedure. This is chaining.
These are all middlewares. But now I'm using again the word middleware. These are proper middlewares. The ones that by definition are middlewares. Next.js's middleware is more of a proxy than a middleware.
So in here it's fine. So let's go ahead and do base procedure .use asynchronous method and go ahead and extract the context and next and go ahead and open this function let's start by returning next and then let's go ahead and first see if we are logged in using await alph from lib alph. So yes, go ahead and import lib alph right here. So alph.api.getSession. Headers are going to be await headers from next headers.
So make sure you have imported headers from next headers. In case there is no session, we are immediately going to break this by throwing a new TRPC error from TRPC server. So just make sure you have imported this. Let's go ahead and add code, unauthorized, and message, unauthorized. And let's go ahead and extend the object that next will send by adding context, extending the context and adding the out object as the session which we just fetched right here.
This way, whatever procedure uses protected procedure, and if the error is not thrown, we'll have access to the current user ID, to whatever we care about for this session. So we can properly query only the documents and records for this logged in session. That is the protected procedure. And I didn't really explain, but I use this three times now. So this thing that I'm doing, getSession, which we also have in outUtils, getSession, it's all documented in the BetterAuth docs.
Let me try and find it. I think I have the concepts API. Here it is, getSession await headers. So that's how you do it on the server side. You need to await the headers.
All right. So now what? Now that we have this protected procedure, let's put it to use. I'm gonna go ahead inside of source, drpc, routers, underscore app, and I'm gonna change the base procedure here to be protected procedure. And now in here, I should have context.
And now in here, I can actually do a console.log userId context.out.user.id. And now, obviously, you can now imagine what you can do here. You can now do find many where ID is context out user ID. So this way you can only query database users which belong to the currently logged in ID. Right, let's actually do that.
Let's query all accounts that belong to the currently logged in user ID. This way you don't even need the console log, you will just logically see whether it works or doesn't. So let's go ahead now, and go inside of app folder, page.tsx here, and let's go ahead and do data here and I think we can just do a wait is it color yes it is color.getUsers and let's go ahead and do JSON stringify data. Let me refresh this. Looks like this is now empty.
Get users. Am I doing this correctly? Users, user find many. Okay, user works. Maybe the accounts are just empty.
But basically, since I'm logged in, you can see that I can successfully now fetch this user here, right? I can successfully fetch my user. But let's just quickly do the following. Let me just add let me add this in a div, let me add null to here, like so. Flex call.
Gap y6. I'm just adding something so they are spacious. Now let's add a button. Log out. On click.
Let me see. Can I do? I can't do log out from here because this is a server component. Okay, let me try and think of something real quick here. Instead of the app folder, just for fun, create logout.tsx.
Mark it as useClient. Import outClient. Export const logoutButton. Mark it as use client, import out client, export const logout button, return a button, logout, on click of the button. Simply call out client.signout.
The reason we need to do this is because we are playing with server components and client components and server components cannot do on click in the button so we need to do it like this by calling out client instead of a use client here so now just render a logout button from .slash logout file there we go So you can see that now this is a protected server component and I'm logged in. So if I click log out now, let's see, am I logged out? Maybe I have to refresh. But yeah, you can see that now I'm redirected here, right? But what if I forgot to do this?
What if this breaks? What would happen now? So, previously, maybe a better example is to not have anything here. Like this. Let's change this to base procedure.
And so let's purposely, yes, forget this and let's purposely bring the base procedure back and remove anything from the find menu. If you go to localhost 3000 now, you will have access to the entire data. So this is what I'm talking about. You shouldn't rely on this. This shouldn't be your last level of security.
Instead, your data access layer should use the protected procedure. And this way, you get the error. So when I tell you that I'm doing this a way to require auth for user experience, this is what I mean. I mean to the fact that the user won't even see the error, I will just redirect the user away from the error. But in case this ever fails, I don't want my data to leak.
And it won't. Because my data access layer, my TRPC, is going to be using the protected procedure in places where it needs to use the protected procedure. That's what I was trying to explain to you previously. That's why I emphasized on creating the data access layer because we're building a production-ready app here. We're not building a toy, right?
So I want you to understand what a data access layer is, why the ERPC is so useful in this project, and the difference between using auth protection checks within server components to redirect, between middlewares to redirect, layouts to redirect, and in actual data access layer to protect your data. That is the difference. All right. I think that's enough for this chapter now. And I'm pretty sure I managed to get my point across.
I hope this made it clearer for you about what we're doing and why we're doing it. So yeah, you should be able to log in, you should be able to register, you should be able to log out. Your root page should redirect you to the auth page and even if this breaks, if you remove this, you should still get the error because you're using the protected procedure. So only once you actually log in and click log in here should you actually see the data. And yes, this logout doesn't work instantly.
So the way you can fix this is you don't have to do it now but basically sign out here accepts I think on success it accepts fetch options and then on success here just to demonstrate the Uconst router, use router from next navigation. And let's just do router.push login. So if I try now AntonioMail.com 1, 2, 3, 4, 5, 6, 7, 8 and click login and click logout here. Still not working. Okay, never mind.
I mean, never mind. Obviously, we will make sure this works, but it's not really what I'm trying to show in this exact segment. I think it's because we are rendering a server component here. So maybe I need to revalidate the server component first. I'm not really sure.
I have to do something but I can't really think of it right now. What's important is that it actually clears the cookies once I click logout, right? And then when I try again after I refresh you can see that it's no longer allowing me to do so. So yes obviously we will create a very reliable logout which redirects the user but for now this is enough. Perfect.
So let me go ahead and check this. So yes, we just added out procedures here. And now let's push to GitHub. So 04 authentication. I'm going to go ahead and create a new branch.
04 authentication. I'm going to go instead of my source control. As you can see, I have 18 uncommitted files. So package lock, package, a schema, migration file. I removed the client file layout, our little logout client button pages where I added mostly the auth protectors, the important route handler for better auth, login form, register form, important auth client util, auth utils which I use to redirect the user away, auth.ts, the most important util for better ALF and then some modifications in the init where I added protected procedures and finally some routers where I used the protected procedures.
So that's the 18 files that we did. Let's go ahead and click stage all changes and let me name this commit zero for authentication. Let me go ahead and hit commit and let me hit publish branch. And once this is published I'm going to go inside of my GitHub repository here. I will open a new pull request and now let's go ahead and review our changes.
Keep in mind that a lot of things that we just did are of course just for demo so CodeRabbit will definitely have a lot of comments and a lot of changes that we have to do, but that's okay. We will change them in the following chapters. What we did in this chapter was to demonstrate how we're going to use out and the dangers that can happen and the data access layer protection that we are going to do to fix it. And here we have the summary. New features.
Email password authentication enabled with session management. New login and sign up pages with form validation and redirects. Auth API route added. Home page now requires sign in. Logout button to end session and return to login.
Toast notifications for out success or errors. Home page content now loads server side after authentication. And we added authentication library dependency. As always in here we have file by file summary but what I'm really interested in are the sequence diagrams and we don't have to go through the entire sequence diagram of course but I just want to bring your attention to something. So this is what I was talking about.
You can see that when the user heads to the GET route we do all kinds of require out and some headers and if we don't have the session we redirect otherwise we call getusers.query But this is only the first layer of our authentication protection. The actual layer of our protection is the trpc.getusers.protected procedure. So the last layer before we actually call Prisma, which has direct access to our database is fully protected. That's what's important, right? So our app would be equally as protected as if we didn't add none of the redirects as well either.
If we didn't add any redirects in the first place, our app will be equally as protected because we protected our data access layer. That's what I was trying to say. Right? I hope I managed to explain that well to you, right? And again, super impressed by how CodeRabbit managed to understand the entire thing that's going on with our app here.
In here, we have a sequence diagram explaining how login works and how register form works, but I think we already understand it's a simple form submit, which then creates a new user in the database. In here, we have some comments on our migrations, which we don't really have to modify Because these are just development migrations. Obviously they are with issues because I just deleted the old user and added a new one without caring for migration at all. I just removed all records from my database. That's what it's warning me about.
:00 In here, I think it works perfectly fine with just updated ads. So I disagree with this comment right here. And since BetterAuth is, we use the migration script CLI to add this schema and BetterAuth works fine with it. So that's why I'm not going to modify this at all. I'm going to leave it as it is.
:19 In here, obviously some very obvious risks here by just, you know, stringifying my data. So we are not going to take that seriously right now because we will remove that. In here it recommends increasing our password security by increasing the length, the minimum length of the password. So definitely something we have to do. And in here it actually noticed a typo.
:43 I typed unauthorized instead of unauthorized. So definitely have to fix that as well. And yes, we also have Google and GitHub to add. Those are mostly OAuth configurations more than anything else. But we will do that of course.
:58 I will see if it will be in the exact next chapter or maybe later on. As I said, there's not much code we have to do here. It's just configuration and working with the third party providers. So for now, let's go ahead and merge this pull request. I'm satisfied with it.
:14 And once we've merged it, let's go ahead and go back in here select main make sure you hit the synchronize changes button click ok and then go inside of your source control once you've merged it go inside of graph and just confirm that it looks like this if you are following along with me regarding the Git workflow. So we detached 404 authentication and then we merged it back inside of the main branch. So now you should have login and sign up inside of your main branch here. Perfect. I believe that marks the end of this chapter.
:50 Let me just see. It does. There we go. We published to GitHub. We created a new branch, new PR and we reviewed and merged.
:58 Amazing, amazing job and see you in the next one.