In this chapter, we're going to implement the organizations feature in our project. And the reason I want to do this now, because it is very closely related to authentication, which is still fresh from our last chapter. So The first thing I actually want to do is I want to implement a component called Alfgaard. So the reason I want to do that is because right now our state of the app is a little bit weird. If you go instead of your web app page dot DSX you can see that we are using this authenticated and unauthenticated components to handle front-end authentication but then we do the same thing in the middleware right so it's not exactly consistent so this is what I want to do next.
Instead of your apps, web, go ahead and create a new modules folder. And inside of here, go ahead and create an ALF module. So this is the file structure that we are going to use. And inside of here, create the UI folder, create components. And now also create layouts and create views.
Like that. And now the first thing we can do is we can create the auth-layout.tsx and this one is quite easy. You can copy it by going inside of app, auth, layout. So just copy the entire thing and paste it inside of auth layout. Except it's going to be a little bit different.
So it's going to be an export const instead of export default and it's going to be called auth layout because this is a specific component for the auth module. And the rest can stay the same. And now what we're going to do is we're going to go back inside of app auth layout.tsx and we're simply going to go ahead and import out layout from modules out UI layouts. This is the way I love to write my apps instead of Next.js. So I like to use the app folder as the router and the loader but I like to develop my components in the modules folder so I have them categorized together right here.
So we just created this very simple replacement of the layout. Now let's do the same thing with the views. So I'm going to go ahead and I'm going to enable signinview.tsx and I'm going to go ahead inside of app, alt, signin, page and I'm gonna copy the entire thing here. Now let's go ahead inside of the signinview and let's paste it here. And let's just do an export const sign-in view here and remove the default export.
And since it's super simple you can copy and paste the sign-up view here as well. And you're gonna see why it's actually useful to do this other than just structuring your app in a second when we develop the Alphaguard component. So let me just rename this to sign up view and change this to be sign up. There we go. So we now have these three new components so now what I want to do is I want to go inside of apps web app out and I want to go instead of sign in and in here What I'm going to do is very simply return sign in view from modules out UI views and then inside of my sign up.
Same thing. Sign up view like this and we can remove the unused clerk import now from both of them here. Perfect. Nothing should really change now. This should all continue to work just fine.
But now we're going to go ahead and create a third component inside of our web modules, auth, inside of components and we're going to call this AuthGuard.tsx and it's going to be marked as use client and we're going to import authenticated from convex react, unauthenticated and outloading. So these three from convex react. And then I'm going to import outlayout from layouts outlayout and I'm going to import signinview from views signinview. And let's go ahead and do Export Const OutGuard here. Let's go ahead and assign the children, react, reactNode and return the arrow function.
And destructure the children from here. And now in here go ahead and return a fragment and add outloading as the first one and now simply add outlayout here. And in here we can just add a text loading like this. Go ahead and copy this and now instead of using out loading go ahead and add authenticated And this will simply render the children. So if we are authenticated allow whatever content is going on.
And for our last one, we can copy the one from above and change outloading to unauthenticated. And again, instead of the out layout, simply add the sign-in view. So what have we just created? We have created an AuthGuard component that we can use to wrap any single thing inside of our app. And while the auth is loading, it will display the loading state in a full screen centered state.
So this loading test will be in the center of the screen. If we are authenticated, we will allow to render whatever we've wrapped this component with. And if we are unauthenticated, we're going to simulate a sign in view. Why not sign up? Well, you can use sign up, but here's the thing.
Our middleware will handle this either way. I'm just trying to make a consistent script here. And the reason I'm even using AuthGuard and not just completely remove it is because you do need to have this out loading because that basically tells you okay convex has loaded the clerk because if you just rely on the middleware itself, it will still work, but your app will get errors now and then because sometimes unauthenticated routes will leak before it loads. So that's why I highly suggest developing this AuthGuard component. And once you've developed it, now I want to go inside of my layout, inside of the web app layout and what I'm going to do now is I'm going to wrap my children inside of the AuthGuard.
And I want to show you what will happen now. So let's go ahead and do turbo dev here. So make sure we are running our app and I'm gonna go on localhost 3000 here because that's where I'm rendering my app here. So right now everything works correctly here. But there is actually a little bit of a problem here because I think that even if you click on sign up, you can see that it breaks.
Something's obviously wrong. So I'm logged out right now. If you are not, make sure you're logged out. You can see that when I click on sign up, something is going on. Something's not correct.
So what's actually going on here? When we added auth guard in the root of our layout, We have prioritized this code over everything else. So you probably know what's going wrong here. When we reach our auth here and when we reach our signup route What happens is that it is trying to render sign up view but we don't let it because we have our rule which says don't render the children, render the sign in view. So that's what's going on here.
So this is what we're going to do instead to fix this issue. We're going to go ahead instead of apps, web, app and just as we've created auth we're now going to create a new one called dashboard like this. You can remove the test one, we no longer need it. And then, go ahead and drag and drop this page.tsx inside of the dashboard. And if you are being asked to update imports for page.tsx, you can press yes.
Basically, what was just updated when I pressed yes was this weird types file. Feel free to save that. It doesn't matter. You can see that now I have this next folder opened. You can just close it.
It is this .next cache folder. It's not an important folder and it gets rebuilt every time you start your app. So don't worry even if you have errors here it absolutely doesn't matter. So now that I have moved my root page.tsx into the dashboard folder I can now create a individual layout file just for this route group the same way I did for the auth route group. So I'm going to go ahead and I'm going to do layout like this and what I'm going to do here is I'm very simply going to add my auth guard here and then I'm going to render the children inside so let me just assign the props here children react react node and the structure the children and the structure the children.
Children. Just like that. So now our auth routes will independently render themselves. But if we encounter, if we attempt to go inside of the dashboard, we're going to protect it both with the middleware and with the outguard. I know this is a little bit confusing, but it is what you need to do to be consistent and consistently use both the convex protection and the clerk middleware protection and now what's important is that you go inside of your root layout here and remove the outguard from here completely and remove the import here and you can see that Now I can switch between sign up and sign in without any problems whatsoever.
And also now if for whatever reason our middleware fails. So let me just comment out this entire thing. And let's try and go to our localhost 3000. Okay, I can't just comment it out. So what I'm gonna do instead is just comment this out.
There we go. You can see the same thing. We're still protected even though I just turned off the middleware and that's because we have protected our entire dashboard which holds our page.tsx here. Oh and we can also remove all of these authenticated, unauthenticated, we no longer need those at all. So you can remove all of them and just leave the normal page here.
And you can also see a little error happening here. So the Error is quite simple to fix actually. What you have to do, and I'm gonna tell you why it happens. It happens because it is expecting this component, Sign-in-View, to be rendered inside of a catch-all route. But we are currently not redirecting to these routes because we disabled the middleware.
What we're doing right now is we're using the auth guard to replace the children with the sign-in view. So simply go inside of your sign-in view and change the routing to hash. And you can do the same thing for sign up view. And once you do that, you're not gonna have any errors whatsoever. But yes, right now we can see that our localhost 3000 is not exactly on the correct route, right?
The correct thing is displayed, but I really like when it redirects me to sign in and sign up. So what I'm gonna do is I'm going to enable back my middleware, but what we've just achieved is consistency. So both our AuthGuard component and our middleware now use the exact same logic. There are no inconsistencies between the two. That is what I wanted to achieve.
So if you now go ahead and log in, you should just see your normal page like this. And you should be able to visit any other page. There shouldn't be any problem at all. In fact, it should be identical behavior to the ones we had so far. So what I want to do now is I want to enable organizations.
So the way you can enable organizations is by going to Clerk, you can use the link on the screen and go inside of Organizations and click Configure Organizations and click Enable Organizations, just like that. And what I want you to change right now is change the default membership limit from five to zero, like this. My apologies, one, and click save. Why are we doing this? So the reason we are adding limited membership here is because we are going to make this organization feature from Clerk self-sustainable.
Organizations are going to be a premium feature in our application, which means by default, each user that joins our app will have their own organization, right? But if they want to invite users, we're going to make sure that they have to upgrade first, Because when you have organizations that have two or more monthly active users inside, that counts as a premium organization on the clerk and it is billable. So This way, your users are going to sustain their costs, which is very important in a SaaS business. So that's why we're limiting the membership to one so that only one user is allowed in organizations. Perfect.
And same thing, I'm not really sure. You can allow users to create as many organizations as they want because Clerk does not bill you for number of organizations, they bill you for number of active monthly organizations, which as I said, is defined as a minimum of two monthly active users in an organization. So that's why we are making sure that by default, our free users will not incur any additional costs. That's why this is important. So make sure that you've enabled that and make sure you have set the limited membership.
And what you can do now is inside of your web app Auth My apologies Dashboard Page.tsx below the user button you can add organization switcher from clerk next JS. And let me remove the sign in button. And what this will now render here, Okay, sometimes it doesn't register immediately. You will most likely get an error because it doesn't register immediately that you have enabled organizations. But now, as you can see, we have our personal account as an organization and I can click create to create a new organization.
But this is a little bit of a problem because I don't really want anyone to have a personal account as their organization. I want every single user of mine to have an actual organization before they are allowed to use my application. So thankfully, Clark has a guide on how to hide personal accounts and how to force organizations. So the first thing we're going to do is we're going to add hide personal prop here. So go ahead and add hide personal to the organization switcher.
And you can see that already it will simply say no organization selected because that is the true state of our app. We are currently not in any organization as you can see right here. No organization selected. So now what I want to do is a similar auth guard but this time it's going to be organization select guard. So let's go ahead and develop that.
I'm going to go ahead and close everything here and I'm going to go inside of my apps inside of web modules, inside of ALF UI components and I'm going to develop organization guard.tsx like this. I'm going to mark it as use client and now I'm going to add a couple of imports. I'm going to import use organization from clerk next js and I'm going to import out layout which we created recently and then I'm going to go ahead and assign the children here. React, react node. I'm going to destructure the children.
And I'm going to return a div rendering the children. So let's go ahead and let's immediately render this new organization guard inside of our app dashboard layout. So right after auth guard add the organization guard. So the first thing we're going to ensure is that we are authenticated and after that we're going to ensure that we have an organization. So this is what we're going to do.
I'm going to grab the organization. ID from use organization. And I'm going to do, if there is no organization ID return, div. Create an organization. Otherwise we don't even need a div we can just use a fragment like this.
And let me just check what are they doing correctly here oh it is organization not organization id so if there's no organization present we're going to show to the user hey you need to create an organization and we're going to do that inside of the out layout actually and there we go you can see that now our user can't continue looking at this app even before it creates an organization. Now let's go ahead and create a organization select view. So instead of auth views here, go ahead and create org select view dot dsx import organization list from clark next js and export const organization list actually let's do org list org select view and in here you're going to render the component we just created and you're going to give it after create organization URL to forward slash. After select organization URL forward slash, you're going to add hide personal and you're going to add skip invitation screen. All of these are important.
And once you create org select view here you can go ahead and import it here. Org select view like this. And I like to be consistent so modules views. Auth UI. There we go.
And you can see that now, every time a user tries to go to our localhost 3000, which is not a sign in page, right? Because auth pages will obviously not be interrupted by this, they will allow you to sign in but if you sign in and we notice you still don't have an organization after you sign in we're going to make it mandatory for you to create an organization first and then again we have a problem because this is now enforced through organization guard but it is not enforced through the middleware. So let's go ahead and do the following. We now need to create our organization select page. So let's go ahead and do that.
So I'm going to go inside of apps, inside of web app out and I will create org selection and inside a new folder catch all org selection like that and inside a page.tsx and I'm going to import my org select view and I will simply do a default page export here and I will simply return org select view. As simple as that. So now my users will be able to, we're gonna have a safeguard if for whatever reason the middleware fails and the user manages to access this dashboard route group, what's gonna protect us is the organization guard, which will not allow it to render the children and instead force this org select view component. But if the middleware works as intended it will redirect the user to forward slash org selection because that is the same way it works for the sign up and sign in routes. So now what we have to do is we have to revisit our middleware once again and we have to modify it so it works with routes.
So what I'm gonna do is I'm going to create a constant is org free route, meaning that for this routes, an organization is not needed. So I don't want that, I don't want this to interrupt the user's flow if they land on the following routes. So create a route matcher, and we can copy this too, because we're absolutely going to have to be able to access these without being interrupted. And the other one will be the org selection. So let me just see how did I name it to make sure you always check org-selection like this org-selection and everything inside so these are the routes which for which I don't want the organization guard my apologies for which I don't want the middleware to redirect because it will cause an infinite loop of redirects this way.
So the user needs to be able to land on this route without any redirect happening for the organization case. So now what we can do is we can bring back our extraction of user ID and organization ID from out. And after we resolve the is public or is it not public route, let's check if we have user ID. And if we don't have organization ID and if this current route is not organization-free route so make sure you put exclamation points here and here then what we're going to do is we're going to create new search params. New URL search params and add the redirect URL to be request.url and create the org selection to be new URL.
In the first argument, open backdex and write org selection. Again, make sure that this route is exactly the one you've named here, org selection, like this. And simply assign the search params to string like that and request.url as the second argument here and return next response which you can import from next server. Return next response.redirect org selection. Just like that.
So what you've created now is the exact same logic that we developed in the organization guard but on the middleware level so again double protection here but also consistent protection so you can see that now when you refresh it looks the same but take a look at your url you are now on the proper organization select url right So if it's not working for you, you've probably misspelled your organization selection somewhere. Make sure that you're being consistent. Right. So what we can do here. Inside of out org selection, maybe we can rename our org select view to be org selection view and change the name of the component org-selection-view.
So this way you don't have to keep thinking, oh, did they name it correctly? So org selection view here. I've just modified this instead of the auth page. And now I also have to modify it, I believe in the components auth organization guard. There we go.
Org selection view. So now the exact same thing will happen, right? If you refresh everything should work just fine. But again, if you go inside of your middleware and let's imagine the middleware fails to work, somehow someone bypasses it. What happens if I try to go to localhost 3000?
I need an organization and if this example is not clear to you what you can do here is go inside of the app, inside of dashboard and create a test route page.tsx sfc page div only authorized only auth and org can see me. So if I try to go to forward slash test without middleware enabled you can see that I'm still being protected because of our organization guard. But the URL is visibly not redirected here. So when you enable the middleware, the exact same thing happens when you refresh, but you also get redirected to the proper route right here. So that is what I wanted to achieve here.
So now let's go ahead and let's create a little company name here. Like that. And there we go. You can see that now you always have one active organization. And if you actually go ahead here and try to invite someone and click send invitations, you will get an error.
You have reached your limit of one organization memberships. That is exactly what we want. We don't want free users to invite new users because that will create additional costs on our account. So this way only those users which pay a monthly subscription to us will self-sustain their own costs, which is what's important in a SaaS business. Amazing!
So I think this officially wraps up everything we wanted to do here. Let's go ahead. We've implemented the Auth Guard and we've properly used the authorized and unauthorized. We moved all of our things to the dashboard, shared route group and the new layout which protects everything. We enabled organizations and we created the org select page as well as organization guard and we limited the members count as well as modifying the middleware to ensure organization is active So if you're wondering how did I know what to write here?
Well, I just followed this guide I just forgot to show you further my deepest apologies here So yes, basically we hide personal on both organization switcher and organization list here. And in here they are showing you many ways you can check for organization if you want to keep it in the URL. Basically, I just kind of extracted a combination of the two of them and did it. They even have some further instructions on how to synchronize with the URL, but we don't really need that in my case. In my case, all I needed was this, the redirect if there's no organization present.
Amazing. So One more thing that I think it's important for you to know here is to enable organization on the backend, because right now in your users, for example, there is no way for you to know if the user is part of an organization, but that's gonna be crucial because we are going to be multi-tenant and we're going to scope some actions and protection only for those users in a specific organization. So in order to do that it's actually quite easy. You just have to revisit your JVT templates here, click on the convex template and in here you have the session token and this is everything that's available for convex. Name, email, picture, nickname, updated ad, family name, all of these fields are available here in the identity.
But you can see that there is no mention of an organization here. So add a comma, add a new field and in here what I like to do is I like to write org id here and simply go ahead and find under organization org id like this and make sure you have a little comma here that's honestly the only thing I need for this project so now what you're going to be able to do is the following const org id will be identity dot org id as string Why do we need to type it as string? Because without it it's going to be a type of any because they can't really know what you're going to add to this claims field. They're not type safely connected. So for this thing, we have to write it like that.
So if there is no organization ID here, let's throw new error here, missing organization, like that. And this way we will be able to store the organization ID in the database and then compare every single time someone tries to access a record, do they belong to that organization ID? So if you've done everything correctly here, you should be able to click add in the web view. You can see how it works. But again, if you revisit your localhost 3001 and let me now just do this.
I'm going to skip this part and I'm going to add a question mark here Simply because I just moved to localhost 3001, if you haven't seen. So go to localhost 3001, make sure it says app widget and click add. Now you will have a different error, missing organization. So I just wanted to show you that that works as well. So you now know everything, your app is now fully protected, nothing can break your app, and you are forcing organizations on localhost 3000.
So the widget will not require authentication or organization, that's gonna be the customer facing chat box. So obviously, we won't burden them with creating an organization or out, right? Why would we do that? We want them to have a very quick way of sending us a new message. Perfect, so right now, if you go ahead and for example, go inside of your organization and delete it, let me just delete it, what's gonna happen is you will get redirected to create organization, right?
You need to have some kind of organization present. And if you sign out, you can see you need to be logged in. So our app is now super duper protected, nothing can break it and we're protected both by the middleware and by our dashboard route group. So yes, just remember that when you develop web yourself, if you do it after this tutorial, you're gonna have to put things inside of this dashboard to ensure that Cleric and Convex are working together on protecting your app, right? So that's why I've created this dashboard route group separated from this out route group because these fields don't have to be protected.
So we are utilizing both the middleware and secure API routes and secure front end protection. Amazing, amazing job. Let's go ahead and merge all of this. So this is called 04 organizations. So I'm going to go ahead and commit all of these 04 organizations commit.
I'm going to go ahead and change my branch to 04 Organizations and I'm going to publish my branch. Once I've done that, I'm gonna go inside of my echo tutorial here and I'm going to open a new pull request and now let's go ahead and review all of our changes for this chapter. And here we have the CodeRabbit summary. So let's go ahead and read from the walkthrough this time. This set of changes introduces a new authentication and organization guarding system in a web application.
It replaces external authentication UI components with internal wrappers, adds guard components, restructures layouts, and introduces a dedicated organization selection flow, as well as updates backend logic to enforce organization context. Several new components and pages were added while legacy authentication logic is removed. Perfect! So in here we have a file by file change summary but what I'm interested in is the sequence diagram here. So let's take a look.
If the user requests a protected route the first guard is the middleware and if the middleware says you are not allowed to do that we're going to go ahead and redirect the user back but if they are authenticated we're going to redirect the user to the organization select. Now even after that if the middleware says you are good or somehow someone manages to fool the middleware. What we do is if the user then tries to access the dashboard, there's another guard waiting for him, the AuthGuard component. And then we don't redirect anywhere, but we prevent them from seeing anything other than the sign-in view. And the exact same logic works for the organization select view.
Both the middleware and our organization guard are doing the same level of protection here. So I really, really like how consistent both of our guards and middlewares are and everything is completely compatible with convex and clerk. This also doesn't include the diagram but we also have protection in our back-end routes so we couldn't be more protected here. It would take a very very good hacker to break into this right now. So in here Code Rabbit is suggesting some completely valid suggestions that I should refactor my add user function but the reason I'm not going to accept this is because this is just a demo page to showcase Convex, right?
Obviously, we are going to add proper loading methods and error handling and everything once we develop proper actions. Now in here, it suggests something interesting and that is that I should be adding useClient to my sign-in view and to my sign-up view and I guess into my organization's? No. I will research that on my own to see if it's true or not. I think that Qlrk has updated since and that you can use sign up as server components but maybe this routing hash changes it.
I'm not 100% sure. In here again, it recommends using the actual identity name here. Again, this doesn't matter. This is just a random API route that we created but CodeRabbit is strict and tells us this is not a good API route because it's not. It's just a test route.
But I'm very very satisfied with this. We just did an amazing job here. As always I'm not going to delete my branch and instead after I merge, I'm going to go back and click down here, select my main branch and click on this push and pull icon and click OK and that will synchronize my recent merge with my local main branch. And when I click on graph here, you can see that we just detached organizations into their own branch and then merged it back here. Amazing, amazing job.
I believe that marks the end of this chapter and see you in the next one!