In this chapter, we're going to go ahead and continue where we left off in regards to authentication. But this time, we're going to focus more on the authenticated states depending on whether the user is logged in or not. But just before we do that, I want to show you some alternative ways that we can persist logged in state. Because if you remember in the last chapter, we had some unclarified thoughts about the cookie name for payload auth token. So we're gonna go ahead and get on top of that.
So what I want you to do is confirm that you're in your main or master branch ensure that you have emerged all of your changes and you can always do git status to double check. And now what I want you to do is I want you to change the way you log in. So there are alternative ways to do that. We are currently doing it using manual next headers where we set the cookie, right? So if I go inside of my out procedures here, you can see that we set the out cookie here.
So this time we're gonna do something different. We're gonna use payloads REST API. So yes, Payload actually offers us local API, which we are using right here, right? So we have this login, which we are doing, await payload login. But we can also do a REST API here.
If we do it using REST API, so let me just find, there we go, so out options. We can just go to slash API, the collection that we need, log in. In our case, that will be slash API users log in. So go ahead and run your app, and you can quickly confirm whether that works for you or not. So go to localhost 3000, and then you're going to go to localhost 3000 API users login.
Why users? Because in our collections, users, we have out set to true. So that's the collection we are using to log in. So go to localhost 3000 slash API slash users login and you should get back an error. But the error should be a very clear JSON error.
You are not allowed to perform this action, right? So that's what we're expecting to see, not a 404 error. We are just trying to access this using a get method, which is not something we should be doing. So what we're going to do is we are going to modify our login screen so that when we submit this data, it actually calls the REST API instead of our login method here, just so you can see the difference. Why do I think that this is a viable solution?
Well, first of all, it's a built-in REST API, right? There's nothing wrong with using it. But the second cool thing about this is that it's going to automatically set the cookie on login. So I think that's the interesting part, right? It's going to be easier for us.
So I want you to go inside of your application here, go inside of your cookies. So go inside of cookies and select the local host and make sure you clear all of them. So make sure there are no cookies here. You want to be logged out. If you aren't sure, you can go to slash admin here.
And when you are in slash admin, it should show you the login prompt, right? And in case that's not the case for you, so let me try Antonio. In case you are logged in, the logout button is down here behind the next icon. So just click the logout button. You can also clear your cookies that way.
Again, then double check in your application here that you have no cookies. There we go. No cookies for me. And now we're going to go ahead and go inside of our login here. So I haven't changed anything yet.
And I'm going to go inside of source, modules, auth, UI, sign in view, so where we call login. And we are now going to modify our login method. The cool thing is, we are using useMutation directly from 10-stack React query, which means that we can easily get rid of trpc here and just rely on useMutation itself. So just remove that part and remove the extra bracket here. So it should just be one bracket and one kind of curly bracket to open the options.
And we have onError and onSuccess, but we are missing mutation function. That's what we are missing. So now we're going to go ahead and do the mutation function. I'm just going to go ahead and comment this out for now, because we don't need it at the moment. And then what you can do here is mark the mutation function as an asynchronous method, get the values, which will be z.infer type of login schema, and then in here simply get the response from await fetch slash API slash users slash login, which we previously confirmed exists, right?
The method is going to be post headers will be content-type application-my apologies, slash json and then body will be stringified values. If response is not okay, we're going to go ahead and get the error from the response and throw back new error so that this on error captures it. In here, we can try to do error.message, or we can default to loginFailed, and return responseJson otherwise. So that's simple for us to modify this login mutation. Instead of using the trpc function, we just added our own.
And this is another good thing for trpc team to decide to separate those two things because you can see how easily we can just do this in case we need it. And the rest of the code stays the same. Login.mutate works just fine. And here is the interesting part at the moment. So if you've done this correctly, make sure you didn't misspell any of this.
So this is why I like tRPC, because all of this would be type safe, right? As of this moment, it completely depends on whether I added any typos or not. So now, as you can see, you might get some cookie from next hot reload, so you can ignore that. So let's try and log in. Antonio at demo.com and demo and let's click login.
And if you do it successfully, you can see that you automatically get your cookie assigned. So that is one potential solution you could do. But still, I want to, in case you don't like this solution and you want to use your login, your auth procedures here, I do want to tell you one way you can improve it. And I suggest that going forward, we together decide what we are going to do, because it's going to be important that we handle cookies correctly, because we're going to have subdomains for each store. So we need to enable cross-domain cookie sharing.
So I would suggest that you follow the same thing I am, regardless of which solution you like at the moment. But the question was, how do we find proof that this payload-token is the cookie we need. We can obviously see it here. That's the name. That's the name the REST API automatically assigns.
But where do we find the proof? Well, what better way than to actually search through the source code of PayloadCMS. That's the power of open source, right? And if you search for PayloadToken, you can find plenty of proof that that is correct. That is the PayloadToken they are using here.
But The story doesn't end there. If you actually just search for dash token, you can find something more interesting. Payload.config.cookie prefix. So, remember how I tried to use Funroad token, right? I wanted something unique.
And by doing that, authentication stopped working for me. I could never find the logged in user. So here's what you can do if you want to ensure that your auth cookie is correct. Instead of doing auth cookie like this, focus on one procedure now. So make sure you are in the login procedure, for example.
What you can do here is the following. Open backticks and then do context database, which is actually payload. And then in here, let me just, I think you can do .config, there we go, cookie prefix, and then dash token. And cookie prefix by default is payload. So this ends up being payload token by default.
But if you want to, you can go inside of payload.config.ts right here. And in the config here, I think you can just pass cookie prefix funroad, for example. And then this is going to change all cookies that payload sets to a different prefix. So what I think is going to happen now is that even if I use the login, which we just implemented, the REST API is going to change it to FunRoadToken. So just to confirm, let's try it out.
I'm going to delete this again. Now I'm going to go and log in and do AntonioDemo.com and demo and log in. And let's see, there we go. Now, as you can see, It's called funroad token. And sometimes this will stay null.
That's simply because we have to invalidate the session. So just refresh. But you can see that now it works with funroad token. So if you want to, that's how you can change the cookie prefix. But here's what I suggest.
Let's not change the cookie prefix. Let's keep it as simple as possible. And another thing I want to do is I would rather we use our base procedures here, But I just wanted to show you that this is a completely valid way of doing it. This works just fine. If you like this, you can use this.
But I would suggest only doing this for login, not for register. Because for register here, as you can see, we do a check for existing user by the username, which is something that their API user's register is not doing. So here's what I'm going to do instead. I'm going to go ahead and I'm going to abstract this cookie creation thing. So I'm going to copy this, and I'm going to go inside of my auth, and I will create a new utils.ts file.
And I will export const asynchronous generate auth cookie, which is an asynchronous method, like this. So getCookies, like this. My apologies. This will be importCookies as getCookies. This will be import cookies as get cookies.
And I want to create interface props here. So let's accept the name. And let's accept the prefix and let's accept the value. So those two things are required here. So we have prefix and we have value.
And then in here, we're going to do the following. We're going to set the prefix-token and this will just be the value. Like this. And you can remove this for now. So generate Auth cookie.
And now, inside of procedures, let's go inside of our login first. I'm going to go ahead and find where I use this cookie. And I'm going to replace it entirely like this. And I will just do await generate-auth-cookie from ./.utils like this. And then in here, I can pass in the prefix from context database auth, my apologies, config cookie prefix, and I can pass in the value to be data.token.
There we go. So using this method, I will ensure that my auth cookie is always properly named and always properly generated. And I'm going to do the same thing here in my register method now. There we go. So now, after I have ensured this works, if you want to, you can also add...
Actually I don't think... If you want to, you can import server only here to ensure that this only gets imported by the server modules and not by client modules. But I'm not sure if it's actually harmful if this gets imported. This is just for setting the cookies. And you can access the cookies anyway, right?
But if you want to, you can add server only, which will cause an error if you try to import this. Yeah, I would suggest you don't add this actually, because it's like your global utils here. Okay, so we can now generate the out cookie from here. And now, what we can do is we can go back to sign in view here and we can revert it to what it was, right? So I'm just going to go ahead and command z this.
Let's go ahead and command z as much as we can until it simply uses the TRPC and instead of mutation function, there we go. It will look like this. But now it should work just fine, right? So I'm going to go ahead and clear my cookies here. And I'm going to go ahead and log in And now my Antonio at demo.com and demo here should work.
There we go. The payload token is set. If I refresh, I am logged in. So why is it called payload token? It's called payload token because we use the prefix here, right?
Context, database, config, cookie, prefix because of that. And we added dash token. So make sure you don't misspell that. Confirm that when you log in, this is the token you get. And you can now remove the auth cookie constant here.
Like this, I'm going to remove the constants. And now we have to fix our... Well, actually, we don't need the logout method at all, because we are never going to be using it like this. So you can remove the logout method, so you can remove out cookie, and you can remove the cookies from the import entirely. So I think this simplified it a little bit so we can still use TRPC, but we are using a bit of a safer way to generate the cookie using the prefix here.
And now another thing I want us to do is that when we log in or when we register, I want to go ahead and invalidate my session call. On my homepage here, we do the session call. So it will be nice that when the user logs in, this has to be refreshed. Because sometimes it can happen that this stays null even after we have logged in. So I went and just quickly opened the TRPC docs so we can learn how to invalidate.
So previously you would invalidate by adding trpc use utils, but now as you can see, you have to do it like this. So we need the query client here. So let's go inside of the sign in view and let's add the query client, use query client from 10 stack react query. Make sure you have use mutation and use query client imported here. And then I'm just going to separate this.
What we're going to do is we're going to await query client dot invalidate queries. That's what we're going to do inside of the onSuccess here. So await. Actually, we don't have to await it, I think. Maybe we do.
I don't know. Let's see. Query client, invalidate queries, and then inside, pass the rpc.auth session. And I have to pass in dot, let's see. Can I just do query options?
Or do I need query filter? I'm not sure why they are using query filter as an example here. But we can put query filter here as well. And if we want to, I think we can asynchronous this and then await this. And then this will be awaited like this.
And then only after we invalidate, we push back to the user. So maybe that could be a cool way of doing it. So now, if you go ahead and log out by clearing your cookies, it should be set to null. But if you go ahead and log in, it's immediately, as you can see, set to your new user. So there's no worries whether that is working or not.
So now let's go and do that in the sign up view as well, because when we register the user, we also log in. So use query client from 10 stack react query like this. And then let's go ahead and just do the same thing here. There we go. And mark this as an asynchronous method.
So now, even if you clear your token again, right. And let's go ahead and create the I don't know, mark demo.com. Oops. So mark mark demo.com and demo for the password. When we create an account, there we go.
We are immediately logged in with that new mark account as you can see, and we have the payload token right here. There we go. So now what I want to go ahead and do is ensure that you cannot visit the login page if you are logged in, right? So if you are logged in, you shouldn't be able to see this page right here. So let's go and go directly inside of the source app, app route group, alph route group, and then let's go in to sign in.
Since these are server components by default, we can go ahead and leverage direct the RPC access to the database, right? So go inside of server components here, or maybe we can read it from migrating. Let me just see. They have some good migrating examples here. But I think they're mostly examples for the client.
Yeah, let's go instead of server components here, getting data in a server component. So this is one example that they have creating a caller. Maybe we can try doing that here. If you need access to the data in a server component, we recommend creating a server caller and using it directly. So yeah, it's important to understand that this method will be detached from the query client and does not store the data in the cache, right?
So we can't just trigger this in a server component and then expect it to be available in the client, which is something that we actually expect at the moment, I think. So if I go inside of trpc server.tsx and export a caller here, right, So using the app router, which we import, .createCaller and pass in the createTRPC context. This is with server only, so it's safe to add it here. So I think that then, if I go ahead and try and do const session using await, how did we call it? Caller from trpcserver.auth.session.
Let's see, that will be a type of auth result. So I can do if there is a session.user, in that case, we can redirect back to the root page. There we go. So this is a super quick way to handle that in server components using a caller. Previously when we did this example, I think we used this, right?
So this is where we really wanted the data both on the server and also adding it to the query client cache. So that's why previously when we did it in the homepage for the server component example, it was more complicated. But looks like there is a much simpler way to do it using color. And I think this is perfect for this example. So if you are logged in now, and you try to go to login, you can see that you are immediately redirected back.
So we can do the same thing instead of a sign up page here. There we go. Just mark this as an asynchronous component here and import the color from the TRPC server and import, whoops, redirect from next navigation. There we go. Just make sure these are server components.
Great. So now, user should not be able to visit neither the login nor the sign up page. So that's one out state taken care of. Now I want this to go instead of app, home, and go inside of the navbar. We are going to move these components, right.
But let's for now just wrap it up here. So we have these two start selling and log in. So what I'm going to do now is I'm going to add the RPC use the RPC from client. And then I'm going to do a session here. And we're simply going to do useQuery here from tanstack react query, trpc.auth, session, query options, like this.
And we can extract the data and call it session, actually. No, let's do whole session, like this. So useTRPC needs to be imported from TRPC client and useQuery needs to be imported from 10-stack React query. And then what we're gonna do is we're gonna check if we have the proper session. So there we go, hidden-lg-flex.
So what we can do here is wrap the entire thing in a ternary. So if we have session.data.user, in that case, one thing will happen. Otherwise, we're going to go ahead and show the login and the Start Selling button in a div, right? So in here, add a new div. There we go, and this div will have the same class name.
And just copy one of the buttons like this. Actually, let's see, Copy the start selling one, actually. It has the design we need. And just add it here. And call this one dashboard.
And redirect to slash admin and don't prefetch this. So remove prefetch for the admin because it's a large file. And now you should be able to go to slash admin every time you are logged in. So you can safely log out from here. Yeah, and after you log out, you will get redirected to payload login page, but we're going to see if we can modify this to redirect us back to localhost 3000.
But there we go. So now you can see that we have access to these. But once you actually, you know, log in here, you have access to the dashboard. There we go, which is exactly the behavior we're going to want in the future. So I think that we outlined one more thing to do here, the dashboard button and also the library button when logged in.
So let's go ahead and do the library button here as well. So we're going to have to do inside of search input here. So search input. And let's go ahead and do const PRPC use TRPC from TRPC client. If you want to, you can abstract.
If you feel like you're repeating the code too much, you can of course abstract this into something like use out, use session, right? But I feel okay for now. I don't feel like this is writing too much things, but of course you can abstract it. And then if you have a session here, there we go, to do, add, we added the View All button, so that's fine. And now in here, to do add a library button, so if we have session data user, and I think we need to do this.
In that case, go ahead and add the button right here. And the button can have a variant of elevated like this. And inside we're going to have a link with an href to slash library. Maybe this will change later. So make sure you just import that link from next link like this and add bookmark.
Let's do check icon from Lucid React like this. And I think it can be OK for now. There we go. So you can see how it looks now. And let's just add library text.
So let's give this a class name of MLMR2. And let's mark this as child so it has the flex properties. There we go. And maybe this is not needed, actually. Yeah.
So there we go. Now we have the library button appearing next to the search input if the user is logged in. And later, we're going to use that to redirect the user to the library page. Let's see, we have some error here. Hydration failed because rendered HTML did not match the client.
Okay, so for now I'm going to leave it like this. I'm not exactly sure why this would cause a hydration error. But there are some ways we can fix it. But for now, I think it's OK like this. Great.
So I think that that marks everything we need to do. So I'm going to go ahead and mark this as complete. So we learned how to use it manually using Next headers. We learned about payload cookie prefix. And we learned that we can also use their REST API, which automatically sets the cookie on login.
We've set different authenticated states and now we just have to push to GitHub. So let's go ahead and push to GitHub. So I have 10 changes here it seems. We remove the constants file, we added the generate alt cookie util, we've modified our procedures to use those utils here and we Enhanced sign up and sign in views with re-invalidation. We added the caller method to our server TRPC, which we then used in these two pages.
We also enhanced navbar to show a different dashboard button and a library button if logged in. Those should be all the changes. I also showed you that if you want to, you can use their REST API method for logging in. You can also use all of these methods if you prefer. For example, for our session method, you can use api slash users slash me.
And I think you should be able to go to slash api users slash me, and you should get back your currently logged in user. Great! So all that seems to be working just fine. Now let's go ahead and create a branch. So 09authstates, git checkout b09authstates, git add git checkout b09authstates git add and git push uorigin09authstates Once you've pushed, you will notice that you are on a new branch here and you have detached from the master or main branch.
You can now go in your github repository and create a pull request and before you merge let's wait and see what our reviewer has to say. And there we go, summary by CodeRabbit. So, new features, sign in and sign up pages now automatically redirect authenticated users to the homepage. Navigation bar has been updated to display a dashboard link for users with active sessions. And a new library button has been added to the search interface, offering a quick access for logged in users.
In here we have an in-depth walkthrough of what we just did, file by file change here, and of course sequence diagrams. This one is for our auth pages which demonstrate what the caller auth session does and how it works to redirect or to allow the user to visit or render the sign in sign up view page and in here we have how it works once we actually log in including the new query invalidation here before we redirect back. It also found the related PR, which makes sense. Our previous PR was for authentication. And down here, it left a comment that we could improve this cookie here with additional attributes.
And we are actually going to do this for the secure here. And we will change the same site, but it's going to be none. And the maximum age here is something that we could do. Yeah, so maybe we can add these things even immediately. Because yes, it recommends lax, because usually you should not allow cookies to go on different sites.
But in our case, we're going to have subdomains, right? So we're going to have to wait to reserve that. That's why I want to do these attributes later, when we actually implement those subdomains, so that you can see that in action. Because if we just add these right now, I think we're going to forget about them, and we're not going to explain what they do in a good way. So for now, I think this is just enough, with HTTP only probably being the most important security feature here, right?
But we could add maximum age. I will explore that. Other than that I am completely satisfied with this pull request so yeah then changes here let's go ahead and merge this once we've merged it I'm not going to delete the branch. I will just confirm that I have it here. There we go.
ALF states. And now we go back to our main branch and git pull origin main or master. And after that, git status to confirm everything is okay. And in here you should see the detachment and then the merge back to your main branch. And that marks the end of this chapter as well.
We successfully pushed to GitHub. Amazing job! And see you in the next chapter.