So what I want to do now is find a way so that we can extend this session of the currently logged in user. So let's go ahead and just confirm one more time that this is working. So I will sign out, I will refresh, I will try to go to slash settings manually. I'm redirected back here and if I go ahead and add new example.com123456 I think that's my login and password I might be doing something wrong no this is the correct one great So we can go ahead and explore the following items. So inside of our auth.ts file here we don't have a lot of things set up besides the adapter and the session but in here we can do something called callbacks.
So above the adapter go ahead and add the callbacks like this. It's gonna be an object and add a comma at the end. And in here we can define different types of callbacks. So we can visit the ALF.js documentation to learn a bit more about callbacks. So this is located in the guides basics callbacks right here if you want to read some yourself.
So as you can see here callbacks are asynchronous functions which we can use to control what happens when a specific action is performed. And here are a couple of them. So we have this sign in callback which can be used to decide whether we are going to allow the user to sign in or not. So even if they successfully quit an account we can still completely block them from ever signing up inside of our account. And this function is way more powerful than doing that logic in, for example, our actions login.
So in here I can technically do the same thing. If email is some user, I can block them from signing in, right? But if you write that inside of a callback then it doesn't matter what method someone is using to log in. Whether they use an API endpoint or our server action, NextOut is never going to allow them to log in if we write that inside of our callback here. So that's one example.
Next we have the redirect. So we are not going to mess around with the redirect. It works fine from default. And I believe you can read more about the redirect callback right here. So it's called anytime the user is redirected to a callback URL, for example on sign in or on sign out.
By default only URLs on the same URL on the site are allowed. And we can use the redirect callback if we ever want to customize that behavior. So this is how it looks on default, right? So if we ever want to modify it, this is where we can do that. In our case, this works perfectly fine and I believe in most applications as well.
So we're not going to play around with the redirect callback too much. And now we have the two important callbacks. JVT and the session callback. So the session callback is actually what returns our session. Which if you remember inside of our app folder when we created protected settings page we use that right here.
So that's the session that comes from this callback session. This return session is what we get right here and as I've just mentioned we have to find a way to extend this session because this is not enough information to us for us to work with. But before we can extend the session we have to extend the JVT which returns our token because the session uses the token to actually generate the session. So let's go ahead and explore that a bit. So I'm gonna go ahead inside of my auth.ts file and first thing I'm gonna do is I'm gonna try and modify my JVT callback.
So for that you can write asynchronous JVT and go ahead and extract the token from the props here and in order to get rid of the error you always have to return the token at the end like this and then what I want to do is console.log the token like that and you can wrap it inside of an object if you want to find it easier in the terminal log. So I'm going to go ahead and prepare my terminal here and make sure that you are logged in right and you can see how when I refresh here this is my token so the name is new the email is new at example.com, my picture is null. And this is interesting, this is my sub, which is actually my user ID. So this is exactly what I can find inside of my database. So you can either go to NeonDB and look at your tables or here's another way you can look at your database if you're using Prisma.
You can run npx prisma studio and that's going to launch it at localhost 5555. So in here if I go inside of the user model here I'm going to go ahead and see a couple of users that I've created and there we go. Take a look at this ID CLQ 081 and if I take a look at inside of my other terminal here there we go CLQ 081 right here. So this is a matching ID of our user. Great!
So we already have the ID inside of here. And here are some other information inside but this is not enough. This is not everything that we need. But token is not the only thing you can extend from this JVT session right here. If you hover over here, you're gonna see everything that you can extend.
You can extend token, the user account, profile trigger, is new user and session. So you can go ahead and play around. For example, let's go ahead and log the user. Let's see what that's going to look like. So if I go inside of my terminal here yeah and I don't know exactly how the user is filled.
I believe that this might only be not undefined the moment you log in. So I don't think that this user field is too reliable to use and I think the same thing is true for the profile. For example, I don't think we even have profile for this one. There we go. You can see how it's undefined, right?
But if I bring it back to the token, you can see the token is much more reliable when we have a logged in user. So for that case, we're going to use the sub from the token to actually load our user from the database inside of this callback and then we're going to pass more information to the final session. So first we have to go ahead and use this token specifically we have to use token.sub and pass that to the session callback. So for now let's leave this as it is. Actually you can leave the console log so that you can follow the flow of information going on.
So this is the callback that we have. Now let's go ahead and add another one. So asynchronous session which can extract the user. Besides the user it can also extract... Sorry not the user, token and session.
Those are the props it has. And the same as in the token you always have to return the session for this to work. And inside of here we can console.log the token but I'm gonna write it as session token so we know the difference. Alright, this is the session token. So let's go ahead and look at our terminal here and there we go, you can see that now I have the session token which is identical to the token from our callback below.
So here's what we can do now. If inside of this token I decide to do this token.customField is equal test like this and if I go inside of my terminal, there we go, you can see that that is now passed inside of session token right so it's both available in the original token, this is the token callback but it's also available in the session token so this is my custom field test and then what I can do, so let's do some another thing here So I'm going to write session token to be token and I'm going to write session, well, to just be session, right? So now we can keep track of both of those. There we go. So now I have the session that user is name, email, image, null, right?
Here's what I'm going to do. I'm going to go ahead and I'm going to write session.user. And let's write custom field again to be, Here's what I'm gonna do. I'm gonna go ahead and I'm gonna write Session.user.customField Well, we can actually transfer it from here, right? So we can now use this token token.customField like this and let's just go ahead and write if session.user just so we don't have the error accidentally like this and now I believe if I refresh here there we go on the settings page you can see how now I have name, email, image and here is my custom field fully working inside of my server component.
So I think this already gives you an idea of how we can transfer the ID from the token inside of this user session, right? So if we want to, of course, you can always write something you want here. So it doesn't matter. You don't even have to use the token to extend the session. If you want to, you can use it completely like this.
You can see how now the custom field is anything. But here's what I want to do. What we want to achieve is we want to get the ID for the current user which we've just established that inside of the token or session token is sub. So let's go ahead and do this. I'm gonna remove this console.log, I'm gonna remove this token so I'm just gonna return the token in the JVT callback and then inside of here what I'm gonna do is I'm gonna write if we have token.sub and if we have session.user in that case session.user.id is going to be token.sub like this so as easy as that and let's take a look now there we go we officially have the ID inside of our session.
So now every single place where we use the session, be that a server component or a client component, we can always have access to the ID of our user. But this was quite easy, right? Because we already had the id inside of token here but what about a completely custom field right what if we want to add a new field inside of our schema for example well we actually do need some new fields So let's go ahead and do that. So I'm gonna go inside of schema, schema.prisma right here, go inside of the user here. And after the password, let's go ahead and let's add a role for our user.
And let's make this a type of user role which we don't have we're gonna create it in a second and let's give it a default value of user like that so in here above I'm gonna write enum user role and I'm gonna give it a type of admin or a type of user like this. There we go. So now we have a enum of user roles which are available for a user model. So make sure that you save this file, give it a default value of user and now what we have to do, well first I recommend that you shut down your app completely and then run npx prisma generate. This will add it inside of our node modules.
I think it's best that we clear up our entire database. A. Because I want to teach you how to do that and B, because I don't want to have any old fields which don't have the user role. So before we do that, let's actually do this. Let's do npm run dev and let's just sign out.
Since we're going to remove some users, I think it's better that we don't mess with the cookies. So let's go ahead and sign out. Of course, I believe this isn't a problem for NextOut. I'm pretty sure they can handle deleted users, but just for development, I don't want you to have any problems while you're doing this. So make sure that you are signed out, make sure that you've updated your schema Prisma, and now let's run npx prisma migrate reset.
So what this is going to do is going to reset the entire database. So it's going to give you a question to confirm that so all data will be lost. Of course only do this in development don't do this in production. And now that the database reset was successful we have to run npx prisma database push so every time that you reset your database you have to run npx prisma database push and after this has been done let's go ahead and do npm run dev like this. And let's go ahead and refresh the login page here.
And if you want to, you can also either keep open the Neon database or npx Prisma Studio. So one of the two, just so you have an overview of your application. So my users are completely empty as you can see here. So if I go back here, I will not be able to log in because I don't have any accounts. So I'm going to create a test account here with the password 123456.
I'm going to create this account and this should add a new user inside of my database. There we go. We have a new user with an encrypted password and there we go. We have a default role for our user and you can see how it has an enum so that I can change it to only one of the two here. And now if I go ahead and log in with this user, so test at example.com123456 you're going to see that I'm logged in with this user right here.
Great! So what my goal is now is to extend the session so that I can actually have access to this role user right here. So how can I do that? Well, we can do that quite easily. We have to go back inside of our out file right here and we have to focus on the token session.
So we first have to pass this to the token. The reason I want to pass that to the token because we can get access to the token inside of our middleware from the request right here and then it's going to be useful for us to know whether someone is an admin or not inside of the middleware because then we can write something like is admin route and then we're going to write the same logic if is admin route and if you are not an admin redirect the user back. So we can create role-based access, right? Role-based access control using the middleware and the token extension. So here's what I wanna do.
I wanna go ahead and I want to import getUserById from data user. So just make sure that you have getUserById. It's very simple. So it's very similar to our getUserByEmail. Of course you can use the getUserByEmail as well, but keep in mind that that's gonna be a very expensive query because ID is a primary key so obviously the query is gonna be much faster for that.
So now inside of this token here what I want to do is I want to go ahead and fetch my user so I'm gonna write the following first if I don't have token.sub that means that I'm logged out. So I'm just gonna return the token, right? No need to do anything here. Then I'm gonna go ahead and write const existing user to be await get user by ID to be token.sub. Then I'm gonna write if there is no existing user I'm gonna go ahead and return the token again and finally we can go ahead and assign the role to the token so token.role is going to be existing user.role like this and let's go ahead and console log the token inside of our session now so I'm gonna write session token the token here and if I go inside of my terminal here inside of my original npm run dev there we go You can see that my session token now has a role of user.
Great! So what I can do now is the following. I can do if token.role and session.user session.user.role is going to be token.role and for now just ignore this TypeScript error. But let's take a look at our app now. If I extend this, there we go.
After my id you can see that I can find a row of user inside of my session. So that's how we can extend the session inside of NextOut. It's actually not that complicated, you just have to know the flow. So first it starts with the token, right? In the token we already have the ID which is stored in the sub field.
And then what we have to do if we want to get more information is get the user from our database. Apparently you can also use the extracts from user and profile but honestly they are always undefined for me. So I'm not exactly sure how we are supposed to use them. If I learn of course I'll make a video about that. But for now I completely rely on something like this.
And this is also why we had to separate our auth and our auth config. Because in these callbacks we are using Prisma which is not working on the edge. So if we had callbacks, which were defined in auth config, then that will be going through the middleware and then it will break the app because it's not supported on the edge. But I believe that inside of AuthConfig we can freely use Prisma inside of providers like credentials because this doesn't run on the edge. This is simply run once the user tries to sign in.
So I think that's why this is working completely fine here. Great! So now that we know how to extend the role let's go ahead and explore how to modify the TypeScript or the types for the session and for the token. So in order to add a TypeScript to this user inside of our session, there actually is a guide for it. So let me go ahead and expand the screen so you can find that.
So it is in Getting Started TypeScript session right here. And you can scroll down here to the adapters. And in here you're going to have module augmentation right here. And they're going to teach you how to extend the existing session user and add a specific field that you want. But here's the thing this actually does not work for me but we're gonna try it out and then I'm gonna show you a solution that I use which works for me but still I'm gonna give it another shot maybe I missed something so they are doing this inside of auth.ts so we're gonna try that as well we're gonna write our declaration here at the top, and we're going to try to fix this error of our role not existing in this user field, because by default, it doesn't exist here.
So let's go ahead and do the following. So let's add this import from NextOut, importing the NextOut and the type default session. Let's go ahead and try that out. So we already have this actually, so we can just add a comma and import default session like that. And then we have to go ahead and declare a module out of the core like this.
So let's go ahead and declare this module so I'm gonna do that here I'm gonna close this then we have to create an interface session we have to get the user and then we have to extend it before we write anything inside using the default session and specifically get the user like this and then this is where we would add our role for example to be admin or Guest or sorry user right? But as you can see, it's clearly not working for me So even if I add it as a string or something, it's not working. So it simply does not exist on type user. No matter how many times I declared this module, this is not working for me. So even if I reload my window, there we go, it's still not working.
So at first I thought okay maybe it's because I'm declaring this inside of this file. So let's try the alternative thing. Let's try this. I'm going to close this and I'm going to create a new file in the root of my application next-auth.d.ts maybe I have to declare it here for example so I'm gonna copy this here and I'm going to paste it inside of next-auth.dts right here and in here let's import next out and let's import type default session from next out and I thought okay maybe that will work so if I remove it from here and if I remove this import now but as you can clearly see it's still not working for me. So I don't know if this is maybe a part of the migration process or maybe they missed something but this is simply in no way working for me.
So this is what I'm gonna do next. I'm gonna do the following. Instead of extending ALT Core I'm gonna be extending Next ALT. So This is what I'm gonna do next. I'm gonna do the following.
Instead of extending ALT Core, I'm gonna be extending Next ALT. So this is what I'm gonna do now. First, I'm gonna write our extended user. So export type extended user is going to be default session user and in here I'm gonna go ahead and write role to be admin or user like this and then inside of here I'm gonna write session user to simply be extended user like this and I'm gonna change this declare module to go directly to next-auth like this. So now looks like it's still not working but if I go ahead and reload this I think that maybe then it will work and okay As you can see now it can recognize it, but now we have a problem that the token.role doesn't match what we just defined inside of here to be admin or user, right?
What you can do is you can kind of explore even further how you can modify the token. But I really didn't manage to do that, especially in the Next.out version 5. I could do it in the old versions, but it's no longer working for new versions. So what you can simply do is as admin or user for example and there we go now you can see that it's working and you can see that user.role is the correct one. Obviously it's not a really clean solution right But I think it works good enough.
But if you want to we can try and extend the token as well. So they obviously have instructions for how to extend the token but I believe it's still not going to work. But let's try it out. So I'm gonna go and remove this as admin user. So let's keep this in an error.
And let's attempt to resolve this. So we have to import jvt from outscore.jvt and we have to declare the module outscore.jvt like this. And in here I should just go ahead and add a role to be optional and add it to be as admin or as user like this. So now if I refresh this for example so I'm pressing Command Shift and P to open this little command here and I do reload window. It's the same thing as shutting down your Visual Studio Code and bringing it back up, but it's faster.
As you can see, this is not working for me. Token.role is just an empty object for me so I don't really know perhaps we can try maybe extending next out slash JVT maybe that will be better and maybe we can import JVT from next out JVT does that have the JVT alias it does. Token.role no if I reload my window I think it's still not working. Yeah, you can see that no matter what I do, this used to work in the previous versions, but now it's no longer working but you know at least we know how to extend the session. Of course this might change in the future they might fix this so that's why I want to show you this guide where they have clearly defined instructions on how to do this right but at least you know these are just types right I know it sounds weird just types but yeah you can technically if you exactly know what you're getting here you can write stuff like as you know you can actually import user role from Prisma client directly So use a role from Prisma Client like this.
And that's the equivalent of admin or user. Right, so you can use that here. But I actually don't recommend importing that here because I think you're going to lose your auto import for the user role. So you can try it, like user role from Prisma Client. And I believe that if I like remove this here, maybe I'm wrong, maybe it doesn't work.
Maybe I just made it up. So I'm just gonna write as string for now. I'm gonna refresh my window. Now I think if I try to import user role, oh it's working, okay then maybe you can add it there as well. Yeah do that.
Replace the admin and user with user role from Prisma Client. My apologies it looks like in my development process I got some bugs doing that but looks like that was just in my session. So we can now import user role from Prisma client in the auth.ts file, and we can use the token role to be expected as user role. And because we extended the session user with the extended user which has a role property of user role there are absolutely no errors inside of our application here and now we can freely... Well nothing has changed here, right?
We were just working with the types now so it's not really important from the functionality of the project, but it does make our development experience much better now. Perfect. And if I go ahead and go inside of my app folder, protected settings page.vsx right here, Let me just try and do session.user. And I think that you can see how it gives you the types of user and their role. So I can safely have an auto-complete for the role here and an auto-complete for the ID here, right?
So that's what's important for our development experience. That's why we care about TypeScript working, because by extending this, it's one thing to just have it accessible and visible here, but it's another thing to have type safety so that we know that we are working with that information. Also, one more thing, in previous versions user.id was not defined. Now it seems to always exist. So usually you had to do the same thing in the extended user.
You also had to add an ID to be a type of string. But now it looks like you don't have to do that anymore. But if you, for any reason, have an error, it's just as easy as adding it here. So ID.string, right? And whatever other fields in the future you might have, right, So you can add a complete custom field here and if you go ahead and write session.user.
There we go. You have the custom field auto-complete and you can assign anything you want here. For example, if I go ahead and visit my code now, you should see the custom field to be anything here there we go so I hope you kind of learned how we can manipulate callbacks in JVT and Session now so just remove that assignment of a custom field and go back instead of next out and remove the custom field from the extended user. We were just doing that to practice. Great and here's another thing I quickly want to show you while we are already here.
So I'm going to go ahead and sign out now and if I go inside of my Prisma Studio you will notice that I have a field email verified to be null. So here's what I'm gonna do. I'm gonna go ahead and I'm gonna attempt to block myself from signing in. So let's go to the top here above the session and let's write asynchronous sign in and in here we can destructure the user and by default let's return true so we allow the users to sign in and in here let's go ahead and see what this user is made of. So I'm gonna write const database, actually let's call it existing user to be await, get user by ID to be user.ID like this.
And then I'm gonna write if there is no existing user or if there is no existing user dot email verified in that case return false. So this way if the logged in user is not inside of our database for any reason if that might happen or if we don't have verified email we are not going to allow the user to log in. So let's try that out. So while looking in my database if I try to log in with this user which doesn't have their email verified it should not be allowed to log in. So if I try test at example.com, one, two, three, four, five, six right here, I should not be able to sign in.
Let's take a look and there we go. Something went wrong. Perfect. So we just finished that. Of course I'm gonna remove this for now.
We're not gonna need it. We're gonna come back to this sign-in. I just feel like it's easier for us to work with when we are allowed to sign in so we can fully see our active session and everything regarding that. Great, great. So I think we did a good job going over all the important aspects of callbacks.
I hope you learned what they do and what they are used for And what we're gonna do next is we're gonna go ahead and enable GitHub and Google sign-in and then we're gonna go ahead and create some email verification. Great, great job!