In this chapter, we're going to be adding authentication to our project. We're going to be using Clerk for that and we're going to follow special instructions to make sure it works with convex inside of our Monorepo architecture. So just before we do that, I think we have an interesting issue from the previous chapter that our AI code reviewer alerted us about. So our SLint dependencies do not match. If you go inside of packages, slint-config-package.json, you might Remember that we added this new package to fix the build error that we had.
Now the issue is the S-Lint version is lower than its dependency version here. So here is a little trick how you can find out what version this is supposed to be. So what you have to do is copy your S-Lint version and then go inside of your terminal and do pnpm view s-lint at and then paste the exact version and in here you will see all the dependencies and their versions and we can find our SLintJS version that needs to be 9.20.0. So go ahead and find SLintJS here and change it to 9.20.0 and that will ensure that for SLint 9.20.1 you are 100% certain that this is the correct dev dependency of SLintJS it needs to have. Obviously, this isn't exactly breaking our project, but it is a useful lesson for us to learn how to do.
And after you have changed that, make sure you are in the root of your application and simply run pnpm install so all log files are updated. And you should have two changes, one in the package.json here, and one in the log file. Now let's go ahead and let's add authentication to our project. So the first thing I want you to do is create a clerk account. You can use the link on the screen to let them know you came from this video.
And after you've created an account you're going to enter your dashboard here. I'm going to go ahead and create a new application and I'm going to call it Echo. As you can see you can choose from a pretty impressive amount of providers here but I'm going to stick with Google and email because they are the ones I use in my personal applications. After you have created your application what I would recommend is that you don't touch anything right now but instead head back to convex.dev or use the link on the screen and after that go inside of the documentation authentication and select clerk And in here you can find very specific steps for adding clerk with convex. Be mindful that the first example is for React.
So make sure that you select next JS or just scroll down until you find Next.js because that's what we're building with. So we already did step 1 and step 2. Now it's time to do step 3. We need to create a JVT template. So let's go back inside of our clerk dashboard, configure JVT templates in the left sidebar under session management and click add new template.
By default it's going to be a blank template so you can select the template input and scroll down until you find convex and click save. What's important here is that you don't modify anything. The name needs to be convex, leave the token lifetime and allowed clocks queue as it is unless you are fairly certain of what you're doing. What's important here is that you copy the issuer URL. So go ahead and copy this URL right here.
For me it is PleasantDuck77. And now let's follow the convex instructions further. So as you can see, they say this as well. Copy and save the issuer URL somewhere secure and do not rename the JVT token. It needs to be named convex so the exact steps that we did.
And now let's go ahead and store that issuer URL under next public clerk front-end API URL. So where do we store it? Well, only one of our apps will need clerk authentication and that's gonna be our dashboard, the web application. So go inside of web, not widget and inside of .environment.local. What I like to do is I like to separate my environment variables by their provider.
So this will be clerk, next public clerk frontend URL. I think it's something like that. Let me double check. Next public clerk frontend API URL. What's important here is the next public prefix so that we can expose this environment variable and access it in our frontend.
Great! And once you've done that you have to modify your auth config.ts inside of your backend package. So let's go ahead and just prepare that file so you know where it is. Instead of the packages folder, find the backend folder convex and then create a new file called auth.config.ts. Let me just confirm that is the name alf.config.ts.
And you can copy the entire content inside. It's not really complicated. So what we're doing here is we are exporting default an object and then the providers array and inside a single object. In here I seem to be having this error that process is not something available here and it could very well be because of my types node missing. So the way you can quickly check if that is true or not or at least the way I would do this if I encounter this error is I would check my other files.
So let's see my UI package which I'm using as a source of truth here. Does it have types node? It does have types node. So let's see if we can very quickly fix this issue by simply running pnpm filter backend add save dev at types node and let me just confirm that that's the package that is missing types node let's see I'm going to add this from the root of my application and once I go back, there we go. The issue was resolved.
And let's look at my new package json. Types node is 20. Perfect. So what I think that pnpm does, I'm not sure and feel free to correct me, but I think that types node was installed for the version 20. It could very well be because this is the latest version.
I'm not aware of what is the latest version of this package, But I think that if in one of your packages you have Typesnode version 20, it will find that in the log file and use the same version so you don't have any mismatches. I think. I'm not 100% sure but That's something that I have noticed or at least think that I've noticed that. Now let's go back here. So replace your own clerk issue URL from the convex JVT template or with process.environment clerk.jvt.issuer.domain.
Now here's what you have to do. What I personally like to do is I also like to add this to my .environment.local. So I am going to add it here as well and you can copy it from your web. So it's this one right here. But if you just do it like that, it's not going to work because they strictly instruct you to add this to your convex dashboard.
Because this isn't reading your local environment, it's reading the convex cloud environment. So what you have to do here, I'm going to, I'm not going to close this documentation. I'm just going to open a new convex.dev here and I will log in. And let's make sure that we are inside of our newest project. So echo tutorial here.
You can see that I am in my development instance and now I'm gonna go ahead and go inside of settings here environment variables and I will click add and cool thing is you can just paste the environment file so when I go inside of my environment local for my back-end package I can just copy this entire thing and paste it like this. I don't have to manually move it around. And that's what we need to do. So clerk.jvt is your domain. Whenever you're working with environment variables, I advise you to copy them as much as possible because typing them manually is a recipe for a mistype and then you're going to spend hours debugging why something's not working when it's a simple issue like a mistype.
So please just copy it from here and then paste it here and then paste that here and then copy that here and ensure that it is the same one here. Great. So now let's go back inside of the docs here. So we just did this step and now it says to deploy our changes. Let's go ahead and do that.
We can do that very easily by just running turbo dev in the root of our application because that runs all of them including npx convex dev. Just to remind you because inside of our package json for the backend our dev script is convex dev so because of that we have practically run this command independently but we run it across all of our tasks here and you should be getting a successful function like this if you did everything correctly. If you haven't done anything, if you are not getting a success message, it could be a small mistake. One of the most common mistakes include wrong file names, typos here, typo here, so as I said please copy these things rather than type it out yourself. And after we've done that, we have to install clerk-nextjs.
But we have to be careful about where we are installing this. So let's use BNBM F web because that is our application where we want clerk. We don't want it in widget. We only want it in web here. And let's do add clerk next JS Like that.
And this will now add clerk exclusively into this package. Jason right here for the web. So now let's see the next steps. Now we have to add our clerk and the environment keys which you can find by going back inside of your clerk dashboard here. Let's go inside of the overview and let me find them right here.
So set your clerk API keys just in case you can't find them here. Let me try and find the API keys page for you so you know how to access them. There we go. So under developers API keys you can find this for Next.js. So copy these two Next Public Clerk Publishable Key and Clerk Secret Key and make sure you are inside of your web app here.
Head inside of the .environment.local and just below this next public clerk frontend API URL, now add next public clerk publishable key and clerk secret key without the next public prefix. That is super important. So clerk secret key is essentially your back-end key, something that should not be exposed to the front end. I'm making a tutorial here and I don't have any credit cards connected to my clerk account so it doesn't mean much to me if someone copies this key and I will remove it afterwards but make sure that you are very careful and don't share this with anyone because they can occur costs on your account if they found out your secret key. Perfect!
So we have that set up So let's go back here and follow further. Now let's add the clerk middleware. So I'm going to copy this very simple example here. And don't worry if you haven't copied it. It's super simple.
You're going to see now. So instead of web, go ahead and create middleware.ts here and paste. So we are importing a clerk middleware from clerk next JS server and we export default clerk middleware here. And Well, this config is not something you're gonna want to write. So let me show you multiple places where you can find this.
One place is here from the convex documentation, but you can also at this point focus on the clerk documentation. So there we go. You can see that on my overview page, before I add any user here, you can see that it instructs me to create the middleware TS in the exact same way. So I can copy it from here too, it's exactly the same. So Let me also show you just one more way you can do that.
I think that in here you can access the documentation and then in here, middleware, next JS. There we go, same thing. So I just shown you three places where you can find this code so you don't have to write it yourself. Perfect, so let's see what next. After we have added Clerk middleware, we have to modify our convex provider.
So if you remember what that is, Instead of our web app, instead of components, we have providers.tsx. And now we're going to have to slightly modify it so we access... So we add Clerk to the convex provider. So what I'm going to do is I'm going to import convex provider with clerk from convex forward slash react dash clerk. Let me just add that here.
There we go. Convex provider with clerk from convex react dash clerk. And then I'm going to add use out from clerk next JS. And then let's go ahead and well, if you want to, yeah, you can add this error message. I mean, I think we should have had this from before, but it's not the end of the world if you don't have it is basically this base next public convex URL without it the app can't work.
That's true. So yes you can add this if you want to. But now let's focus on the clerk again. So now we have to let's see convex react client. So this stays the same but we are no longer using convex provider here instead we're using convex provider with clerk so just replace this existing one with that and we have to add a new prop here called use out So let me just assign use out here.
There we go. Use out and this should now work just fine. And we have already wrapped the clerk. Oh, so Let me just see. So, I see.
Okay. So I'm gonna go ahead and import clerk provider here. Actually, I will follow the docs to a dot. So I'm gonna go inside of web layout. Let me just go, my apologies, app folder layout.
So we already have the providers. So I'm going to for now just do the exact thing they do in the docs, just to be sure. So they import clerk provider from next.js and they've wrapped the convex provider around like this. I'm 99% sure we could have done this in the providers tab as well. But I just want to stay true to the documentation just in case I'm not thinking ahead of something that might happen because of maybe this or maybe this needs to initialize first, I'm not sure.
So that's why I'm gonna follow exactly as the documentation says. And now let's go ahead and let's show UI based on authentication state. So at this point, I think that we can safely do TurboDev and we can focus on our web task which is running on localhost 3000. So let's get that open here. Right now all it should do is just load our convex database users here but now we're going to mark that as authenticated.
So let's go ahead and go inside of app page.tsx right here. And I'm going to import authenticated and unauthenticated from convex react. Well, all of these can actually be imported from the same place like that. And now let's go ahead and wrap this in authenticated like this. And let me just wrap this entire thing in a fragment here.
And let's go ahead and add this as unauthenticated. And in here I'm just going to say must be signed in. And I think we can also add authenticated loading. Or maybe not. Let's just leave it like this.
So now, you can see that it says must be signed in. And I think that even in their example, they also add the sign in button and the user button from clerk next JS. So let's add those two sign in button and user button from clerk next JS. So in the unauthenticated, let's add the sign in button. Sign in.
I think that's how you use it. And in here, let's add user button, which you use like this. So let's try that out again. So you should have this sign in text, which when you click should redirect you to the login page, which right now you're probably noticing is a different URL. Don't worry, we're going to change that as well.
So for now, let's just try creating an account. So I've used Google login and after that, there we go. I am redirected back and I can see my user button here, which is where I can find some information about my account here. I can go inside of security to see your IP address and all the places you're logged in from and the place where you also sign out from. Perfect.
So that is what we wanted to achieve, but There's one problem and one inconsistency now. So you can handle protected and public states in many ways using convex and clerk. One way is by using these authenticated and unauthenticated states. Another way is on the backend where you use the identity. So I think they have a short example of that as well.
There we go. Use authentication state in your convex functions. So you should Always make sure that you have identity inside of your convex functions. For example, let's do this now. I think it's important for us to explore this so we learn.
Go inside of your packages, backend convex, and go inside of your users here. And for the add method, let's go ahead and make sure that from context, we can await context out, get user identity. And then we're going to check if identity is equal to null, it means that we are not authenticated and we don't let the user create a new account. And now what I'm gonna do is, well, here's what we can do. We can just go to forward slash, we can just go to 3001 because in here we have a widget.
And if you try adding a user now, you're going to get an error because you need to be logged in, right? You remember that in the widget, which is running on 3001, we didn't add authentication, right? So this is screen is still normally showing even if we are not authenticated, but you can see that our API is still protected. So even if our front end validation fails, our backend routes are protected. That's what's important.
And if you go back to 3000 to apps web, you can see that we must be signed in to see this in the first place. So let me just sign in again. And now in here, I should be able to add users. There we go. I can add users here because I am signed in.
Perfect. Excellent. So let's go ahead and see what else we have to do. So the next is tutorial for something else. But what I wanted to talk to you about is that we now have an inconsistency because we are using these components authenticated and unauthenticated in convex react but our middleware which should technically do the exact same thing isn't doing that So I want to show you how you can use the middleware to do that.
So let's go ahead and let's import from Clerk middleware here createRouteMatcher and in here I'm gonna set const isPublicRoute and I'm going to set createRouteMatcher like that. And this is what my public route will be. My public route will be all sign in routes which we don't yet have but we're going to create all sign up routes and that's gonna be it. That's the only thing that's gonna be my public route. And now inside of the clerk middleware what I'm going to do is I'm going to extend it to be an asynchronous method which gets the auth and the request like that.
And from here, I should be able to extract user ID and organization, my apologies, no organization yet, just user ID from await auth. And you can import auth from here, I believe. Actually, my apologies, we have auth, sorry. And then I'm gonna check if not is public route and just passing the request I'm gonna go ahead and trigger await out dot protect Just like that. So that's the only thing I actually have to do.
I don't think I have to really check for... Let me just see. I don't think I have to even do this. I think this should be enough. So now even if I don't have the, let me just try it like this.
Let's go inside of web app and let's create a new test folder and inside page.tsx and in here let's go ahead and do an export default page test page. So what's important here is that you do a default export and that it is called page.tsx inside of a test folder. And now we can go to localhost 3000 forward slash test and you should see the test page like that. So here's what I'm gonna do. I'm gonna go back and I'm gonna make sure I am signed out.
And then you can see that already this is working. So if I try going to localhost 3000 forward slash test I'm immediately redirected here. So that's what we just achieved with the middleware. We didn't have to use the components here, but let's try this. What if I add forward slash test here and then manually go to localhost 3000 forward slash test.
You can see that I can now load it even though I am not logged in because we have allowed it to be a public route. And you can probably notice that you can use the same logic to do the reverse, right? So if you will have more public routes than private routes, then you can use isPrivateRoute. And then test will be the private route and then just use that here and reverse the logic, right? So however your app works, you can adjust it.
I'm gonna be using the public route method because I will have more private routes than public routes. So we just use the test to demonstrate how you can still control your authentication methods even without convex components like unauthenticated and authenticated. So what I want to do now is I want to fix this little feature that when we get redirected, we get redirected to another page. This is not on localhost. So let's go ahead and fix that by going inside of apps, web, app and in here let's create a route group called alph.
So route groups are defined inside of parentheses and they are not going to become the part of the URL. So you've noticed that when I create a page file instead of a folder in Next.js, that becomes the part of the URL. But when you add that inside of parentheses, that doesn't happen, which allows you to structure your folders more cleanly. So I'm going to create a sign in route. And then in here, I'm going to create a catch all and sign in again.
And then I'm gonna do page.tsx and I'm gonna explain what this is in a second. Just make sure for now that you are using double square parenthesis here and spreading the sign in. Make sure it says sign in here, sign in here. And in here I'm just going to go ahead and return a page and I'm just going to return a sign in from clerk next JS like this. And then I'm going to copy this and paste it and I'm going to call this sign up.
And then in here, I'm going to go ahead and rename this to sign up as well. And in the page, I'm going to import sign up. Just like that. So basically what we're doing now are further steps in Clerks. So right now, if I go ahead inside of the documentation for Clerks here, we already did the quick start for Next.js, right?
We added the Clerks middleware here, we added the Clerks provider, all of these things are done, we've tested those things. And now in here we have next steps, creating a custom sign in or sign up page. And we just created this. So that's how I knew what to do because I already know it by heart, but this is where you can find that information. And if you're interested in what this weird syntax is, it is the Next.js optional catch-all route.
So basically it's going to allow us to add a bunch of parameters inside of there so that we can redirect the user back to where they wanted to access while they were not logged in. And in here, they are not using the ALF route group. It's just something I like to do. I like to keep all of my authentication routes together. I don't want them to just be on the same level as test and page.
So that's why I'm using the route group. I think it's a cool feature that Next.js allows us to do. And you import these components sign in and sign up. That's exactly what we did. And we already created the public route for sign in instead of our middleware.
Let's just double check that middleware. We already did sign up and we also prepared for sign in and I think we created both routes here and now what we have to do is we have to add these to our environment variables here So let's go ahead inside of the web app here, .environment.local here, and under clerk add next public clerk sign in URL to be forward slash sign in. Next public clerk sign in fallback redirect to be a forward slash and the same thing for sign up like that. And now let's go ahead and just finish the next step which is custom sign up page. So we already did this, We already made the sign up public and now we just have to add our next public clerk sign up URL.
Just like this. And now that you've done this, I'm fairly certain that we are ready to test our app. So let's go ahead. Let me just see where am I here. So let's go back to localhost 3000 here.
And you can see that now we are seeing our sign-in route instead of seeing someone else's sign-in route. And what you can do to center this is very simple actually. Because we are using this route group we can leverage it by adding a layout file here. And you can go ahead and create a layout, extract the children from here, give it a type of React, my apologies, children, children are a type of React, React node. And in here, You can return a div and render the children inside and create a class name here.
Flex, let's actually do minimum height of screen, minimum width of screen, height, my apologies, flex, flex column, item center and justify center. So I'm going to explain what this layout file is but you can see it's a reserved file name that also needs to have a default export and what it does is when it's placed under a route group like out it will use the same wrapper around all routes that it is next to. So now you can see how this is centered. And I think I just forgot to do, Maybe I need to do height full. Flex call item center, justify center.
There we go. Once I do that, it is fully centered. And regardless if I am on sign up or sign in, it's going to be the same thing because if I click on sign up, there we go, same thing. So that's how you leverage the layout file and the auth route group to create common styles for two different routes, because we want both of them to have the exact same centering method. So that's what we achieved with the layout.tsx files.
Perfect. Let's see if there's something else that we have to do now. So I think this is a good place to wrap up the chapter. We still have this weird state where we are protecting both with our middleware but then also with our authenticated and unauthenticated components And the truth is we're going to need both of them, but just not in this way. We're going to create a new component in the next chapter that will be used to properly protect this in a even different way.
But I think this is a good place to start because we are starting to dive in into some more Next.js specific stuff. So I don't want to confuse you too much, but basically this out folder is a special folder in Next.js that allows you to structure your routes. The reason it's used to structure the routes is because itself it's not a part of the URL. You can see that whenever you create a new folder instead of this app folder and put a page file inside of it, it means that this folder will become the URL. But when you add parentheses around, it will not become a part of the URL and it will only start becoming the part when it reaches the first normal named folder.
And this specific clause is a catch all that Clerk recommends when building the local sign in and sign up views. So I personally never really use it, but I think for you it is enough to know that Qlrk requires this so it can pass all the random parameters and so that it can redirect the user to where they come from once they log in. That's what this is. It's just some special folder conventions inside of Next.js. So what we learned in this chapter was how to connect Clerk with Convex using a JVT template.
We've learned how to add the environment variables to our packages and to our web components. We have modified the Convex provider here and we also added some environment variables to do convex cloud dashboard and then we learned how to protect our app in three different ways. First, we use the normal authenticated and unauthenticated components. After that, I told you that you can protect your back-end routes using the identity which is exactly what we did for whoever wants to add new users. We made sure that they have a context.auth available and if they don't, we throw an error and we saw an example of that when we went to localhost 3001 which is where our other component other app is hosted and you can see I keep getting errors because I'm not logged in in this different application in our monorepo so that is working perfectly because this is the actual important out, right?
Front end can always be hijacked in some way, right? It's just JavaScript, but this is on the backend. This needs to be protected. Ironically, this is also JavaScript, but I think you get the point that I'm saying, right? And then we learned how to use the middleware, which is another layer of security.
And in my honest opinion, you should never rely on the middleware to protect your stuff. So As I said, the most important thing here is the backend. That's what's important. That's what you need to protect your routes for. So you shouldn't ever do, you know, forward slash API.
Imagine this says protected routes. So you shouldn't put your API folder inside of the middleware and just think, oh great, API is protected. No, never do that. Always explicitly protect your API routes like this. Always, no exceptions.
Excellent, so I think that we are ready to end this chapter and review our pull request. So let's see everything that we did here. So I'm just gonna go ahead and close some of the things here. So we have a clean slate. A 03 Clerk Authentication.
So I'm gonna go ahead instead of my source control, Always be careful not to accidentally click this one because this will discard all the changes. Always click on the plus icon here, stage all changes. Now it says staged changes, 03 Clerk Authentication. And let's click commit. And before I click sync changes, I'm gonna go down here and I'm gonna click create new branch 0.3.
Clerk Authentication. And then instead of sync changes, I'm gonna click publish branch. And once that is done, you can go inside of your github and click compare and pull request Let's go ahead and create this pull request and now we're going to go ahead and review it. So let's see what we did in this pull request. Summary by CodeRabbit.
We added authentication integration using Clerk, including sign in and sign up pages and the middleware to protect routes. We also introduced a test page accessible within the app so that is the test page that we created right here. The main page now displays content based on user authentication status, showing user related actions only when signed in. So that is our convex React authenticated and unauthenticated state wrappers. We enhanced provider setup to support clerk authentication across the app.
End user creation now requires authentication, that is our API protection here. Perfect! Amazing, amazing job. So in here we have a more in-depth walkthrough. So this is what we did with our ALF route group and the layout files.
So we created a new centered layout for authentication routes and new sign in and sign out page components rendering clerks sign in and sign up respectively. We added app layouts and providers and we added authentication middleware, authentication aware main page, a test page, clerk integration and out convic within convex as well as some dependency updates such as slintjs downgrade and node types so we can access process.environment. In Here we have an interesting sequence diagram explaining exactly how our app works right now and this kind of stuff will come in very handy especially when we start to build more complex things. Great! Amazing, amazing job.
I'm going to go ahead and merge this pull request right here and then what you're going to do is go back inside of your main branch and press on these two buttons and click ok and that will synchronize all changes and then when you go inside of your source control and go inside of the graph you can see that we detached to create clerk authentication and then merged it back to our main branch. So just for a sanity check make sure you have sign in pages for example. Perfect, amazing job. I believe that marks the end of this chapter. So we resolved the SLint issue, we set up Clerk with Convex, we created an account, JVT template, middleware and pages, and we completed the GitHub process.
Amazing, amazing job and see you in the next chapter.