So now let's go ahead and let's set up our OAuth providers, Google and GitHub. So if you're logged in, go ahead and log out so you can see the login screen right here. And the first thing I want you to go through is to localhost 3000 slash API slash out slash providers. And right now the only thing that you should see is the credential providers because that's the only thing that we have inside of our AuthConfig. Right here we have the providers and the only thing we use is credentials as you can clearly see right here.
So what we're gonna do now is we're gonna go ahead and import GitHub from next out slash providers slash GitHub and we're gonna go ahead and use this and add it just above the providers here. So once I save this and refresh this page, there we go. Now I have my github provider here and we can do the same thing for Google. So let's go ahead and copy and paste this one, let's import from Google and let's rename this to Google and then we can just as easily add Google at the top here and then when you refresh this page again there we go now we have Google, GitHub and credentials. Great!
So what we have to do now is we have to obtain some environment keys. So first let's do them for GitHub and let's define them inside of these providers here. So open an object and define the client ID to be process.environment.github underscore client underscore id and then client secret to be process.environment.github underscore client underscore secret like that. And then copy this first variable and immediately add it to your environment file like this github client id and then do the same thing for github client secret so just paste it here. Now let's go ahead and obtain those.
So you have to go to github and go inside of your profile right here on your settings so you can click on the sidebar here and find the settings here like this. Then scroll all the way down and go to developer settings and in here go inside of OAuth apps. Go ahead and click create new OAuth app and give your application a name. So this is going to be a tutorial for me. And then let's give our homepage URL.
So that for me is HTTP localhost 3000, like this, without the slash at the end. So just leave it at 3000 to be the last thing. And now we have to find our authorization callback URL. So we can easily find that inside of this API Auth providers. We have the callback URL written right here localhost 3000 slash API Auth callback github.
So let's go ahead and copy this And let's go ahead and paste it here and just remove the annotations. Make sure you have no annotations at the beginning or at the end of your application. And go ahead and click register application like this. And there we go, you can now obtain your client ID. So let's go ahead and assign the client ID like this and now we have to get our github client secret.
So let's go ahead and click on generate a new client secret and there we go you should now be seeing your github client secret here so copy this and let's paste it here there we go github client secret and github client ID perfect so now let's do the same thing but for Google so go back inside of auth config expand this object right here and assign the client ID to be process.environment.google underscore client ID and client secret to be process.environment.google underscore client underscore secret as well. Like that. Great. And now we have to obtain both of those so let's add them to our environment files. So github client id and...
Sorry, Google client id and Google client secret. So in order to obtain those go ahead and Google Google API console and click on the link which goes console.cloud.google.com and that will open up a console similar to this. So in here first step is to create a new project. So click on this navbar at the top and click new project. Let's give this a name of Auth Tutorial and let's click create right here and now just wait a second for this to be created.
Once it's been created go ahead and click select project and ensure that in your navbar your new project is selected. And now go ahead and click the search and search for APIs and services. So this one, APIs and services like this and click on this one. So it opens the actual page for that. And in here first let's define OAuth consent screen like this and go ahead and select the external user type so anyone with a Google account can test out the authentication.
Give your app a name, so AuthTutorial. And go ahead and select your user support email, which should auto-complete from your current account. You can skip the app logo and app domain, and you can also skip authorized domain. So we're going to add this after we deploy because right now we cannot add localhost here. And go ahead and add contact developer information, email address here, and click save and continue.
And once you've done that, scroll down and just click save and continue so no need to add any special scopes here and no need to add any test users just save and continue and there we go you're gonna see your entire thing right here and now let's go and click inside of credentials here and let's go ahead and click create credentials and let's go ahead and create OAuth client ID. So inside of here select the application type to be a web application. You can leave the name as it is and now we have to add authorized JavaScript origins and authorized redirect URIs. So let's go ahead and write HTTP localhost 3000 for this one and for the authorized redirect URLs we have to use the one defined in here callback URL so just copy this one go back to this and paste it here and of course remove the annotations like that and make sure you don't add a slash at the end here or a slash at the end here so make sure it ends with Google like that and go ahead and click create and there we go You now have the client ID so we can copy this.
So Google client ID and we also have the client secret so copy the client secret as well. And let's paste it here. There we go! So we have everything we need now. So what I want to do now is go ahead inside of our app folder, sorry components, auth, social right here and now we have to implement the sign-in functionality from here as well.
So you already know that there is a certain way we can sign in by using the auth.ts from where we export the sign in function but we can only use this in server components or in server actions like we do here in the login we import sign in and then we use it here and we specify credentials. So we can technically create a new server action which will simply call await sign in and then in here it will be Google right we can do that but I want to show you a different way of doing it just in case you're wondering you can do it still completely inside of client components without server actions as you could before. So for that let's go inside of components out social right here and the first thing I want to do is create this common on click button function here. So let's go ahead and write const on click to accept a provider which will either be Google or GitHub. And in here Let's go ahead and let's import sign in from next-auth-react so that's what you have to import if you want to use it purely in a client component.
So go ahead and select the pass in the provider param And then instead of having redirect to, as we have if we import it from out, which is used for server, right? In here, we have a callback URL. So let's define the default login redirect from at slash routes to lead to there. And now let's go ahead and use this on click here first for the Google button. So on click and passing Google as the provider and in here pass in GitHub as the provider.
And now let's go ahead and test out both. So here I have my Prisma Studio open. You can run it by running npx prisma studio. I only have one user in my database. And now let's go ahead back to our login page and let's go ahead and click on GitHub here.
And there we go. You can see that it's asking me to authorize this website and once I click authorize I should be redirected and I should be logged in on my settings page. And there we go. And you can see that now I have some new fields. For example, my image is automatically filled with avatars from GitHub.
I also have the email which is connected to my GitHub account and I still have the role user and I still have my ID here. And if I check my Prisma Studio and refresh my users, I have a new user here like this and you can see that I also have a link with an account because this is an OAuth sign in so I have a proper relation with my account where more information is stored like what is the provider, what is the access token, the token type, the scope, more things if you are interested in that. Great! So now let's go ahead and try this out with Google provider. But here's the thing, if you try and log in with the same email that you just used for GitHub you're going to get an error.
So if possible try and choose a different email just to test this out. So something you don't have in your database. So make sure you try with a different email, right? So I'm going to go ahead and do that. And there we go.
So I'm logged in with a different email here. And there we go. Now I have an image from Google right and I also have the role user and I also have the ID perfect and if I go ahead and refresh my Prisma Studio here there we go now I have three users So it's official our OAuth providers are now working. But here's what I want to do now. Email verification is only going to be needed for credential users because Google and GitHub already do email verification on their own, right?
Google in itself needs to have two factor authentication to confirm that the user exists and phone number verification and github simply uses a verification email link. So there's no point in us doing that as well. That's the point of OAuth providers so that they offer users a seamless sign-in and they offer you security that it's not a spam email. So here's what I want to do. Go ahead and select these two users in your database which are logged in using github or Google and go ahead and delete those two records from your database and that is automatically gonna delete the accounts as well.
So if I go into account here you will see that I have no rows once I remove those two users. And here's what we can do now. We can go inside of auth.ts here and besides callbacks there is also something called events in NextAuth. So let's take a look at what the documentation says about events. So events are asynchronous functions that do not return a response.
They are useful for audit logs or reporting or handling any other side effects which is exactly what we need right now. So for example we have an event sign in. So when a user signs in, if you add that to the event object inside of Auth you can do whatever you want here, right? You can also check if it's a new user. So if you want to add a different field or something, you can do that.
And that's going to be asynchronous and it's going to be useful for audit logs, for example, if you want to add some logs here or if you want to do some side effect. We are specifically interested in a link account. Why are we interested in this one? Well if this event is ever triggered that means that the user just used an OAuth provider to create or log in inside of their account. Also you probably noticed that we don't need a special register for OAuth.
So it's very simple if it exists it's gonna log in otherwise it's gonna create a new account. So we don't need a... This will work equally we reuse this component here right it's exactly the same We don't need a special login for using credentials. So where was I here? So we're going to use a link account so that whenever someone creates an account using Google or GitHub, we're going to automatically populate this field called email verified.
Because again, there's no need for us to verify an email coming from an OAuth provider. I mean except if you chose a really shady OAuth provider of course but we are working with Google and GitHub here so we can trust them completely. So let's go ahead and let's do that. So I want to go inside of auth.ts here and let's go ahead and let's add events And in here I'm going to use the link account right here. And inside of link account we can go ahead and extract the user and let's also mark this as an asynchronous function.
And in here let's go ahead and simply do await database.user.update where we have a matching ID of user ID and the data we are going to update is the email verified field to be new date. So it's not a Boolean, it is a date field. So we know where an email, when an email was verified. So in the future, if we have some new rules for email verification, this is gonna be very useful because we can simply query and find all users which have not verified their email in a long time. So it's better than a Boolean which is true or false.
That's it. That's all we have to do. And let me tell you what I was talking about. In the model user, email verified is a date time, which is optional. Great.
So confirm that you only have one user in your database which should be the credential providers, you know, email name and password here with the email verified null. So now if I go ahead and use github for example to log in what should happen is a new account should be created and besides that if I go inside of my Prisma Studio here and refresh this event link account there we go has immediately verified the email we know that everything is fine with this account no need to verify anything further here. Perfect! So that works just as we expected. But now we have to talk about this little error that we can get.
So this is my current email which I use for GitHub, right? So what happens if I go into my Google account and use the exact same email to log in? Let's try it out. Well, as you can see, I'm redirected to this weird page which we've never seen before. And you can see that we have an error to confirm your identity sign in with the same account you used originally and in my URL I have an error here.
So how do we first of all how do we not show this page? So this is great, this comes auto-generated from NextAuth, but I want to use my own pages. I don't want to use this pages. It's great that they exist out of the box for sure but how about we use our own? Well we can do that as well.
What we have to do is we have to go inside of auth.ts here and let's go ahead and do the following. Above events add pages And in here first let's define our sign-in route. So for us that is slash out slash login. So now next out is always going to redirect to this route when something goes wrong. And let's also add an error route to be slash out slash error.
So if something else goes wrong, regardless login or just something breaks, I never want the users to see this page. I want them to see my nicely designed page instead. So you obviously know that we don't have this page so we have to go ahead and create it. So it's going to be quite simple. Let's go ahead and go inside of app, auth, create a new folder called error and inside a new file page.psx.
Let's go ahead and import the ALF error page or error page, it really doesn't matter and actually not a div, what I want to return is an error card which we don't have yet but we're going to create it in a second here. So let's go ahead and create the error card by going inside of components out create a new file error-card.tsx. Let's go ahead and let's import the header from .slash header or components out header. Let's import the back button from .slash back button or components out back button. And let's finally import everything we need from components UI card.
So that's the card, card footer and card header like this. And let's go ahead and export const error card here and let's return a card, oops. Let's return a card with a class name of width 600, sorry, 400 pixels, shadow medium. And let's add a card header here with a header component and let's give it a label of oops something went wrong and a card footer with a back button component which will simply have a label back to login and href of auth login like this Great! And now let's go inside of the auth error page and Let's import the error card from components auth error card.
So now if you visit localhost 3000 slash auth slash error like this Oh, looks like it automatically redirects us to here and I believe that is because we have to add that inside of our routes.ts here so we have to make that an auth route so let's go ahead or even better let's make it a public route actually no let's make it an auth route I think that makes more sense auth error Because it's only gonna happen for logged out users. Yeah. So let's make this inside of out routes here. So now if you try and go there again, out error, you should see, Oops, something went wrong like this. Perfect.
And I just remembered that I could have probably used inside of my components out, where is it? Error card. I think I could have used the card wrapper. So let me just try it out. Card wrapper.
Yeah, I think I could have used the card wrapper. So let me just try it out. Card wrapper. Yeah, I think I could have used that. So I can just give it a header label of, oops, something went wrong like that.
I can give it a back button href to out log in and a back button label of back to log in like that. And in here, I can add a exclamation triangle icon from Radix UI icons and maybe wrap that in a div with a class name, WFullItemsCenter and a flex, maybe justify center. There we go. And give it a little class name of text destructive Like that. All right, that looks fine.
And yeah now we can remove all the imports besides card wrapper and I'm just gonna use components out Like that So just a little bit of styling obviously it doesn't actually matter for the tutorial itself and we can go back to the login. Great! But let's go ahead and try this again. So I'm gonna go ahead and use the Google login and I'm gonna select the same email that I already have inside of my database here. So what probably happened is that you didn't get redirected to the error page instead you came back to the login page.
Well that's an improvement from what previously happened which just redirected us to that weird page which we've never seen before. But here's the thing. If you take a look at our URL we now have the error inside of our URL. So we can use that to display the error here instead. So the reason it didn't redirect us to that error page is because that's used for something else right but we have that covered as well but let's go ahead and read from the URL now inside of the login form and let's go ahead and show the error inside of our little form error box at the bottom.
Let's go back inside of AuthForm. So components.auth.loginForm right here. Oh, and we have some errors here. Okay. Well, we'll solve that in a second.
First, let's go ahead and let's import useSearchParams from next slash navigation. Like this. Let's go ahead and let's get the search params so const searchparams are used searchparams and then let's get the URL error to be search params.getError and now let's go ahead and compare it. So if it is our current error in the URL, So I'm going to copy it from here, which is OAuth account not linked. Or you can just simply see what I write here.
So OAuth account not linked, the capitalization also matters. So if that's the case, we're going to write email, or we can write, please log in with, or you can just say email already in use with different provider. Alright something useful otherwise an empty URL error and then you can go ahead and use this URL error down here in the form error. So make sure that the error goes first and then pipe pipe URL error. And there we go.
Email already in use with different provider. So I'm going to go ahead and refresh this and clean the error from my login. So I'm going to try and log in again and see if it appears. And there we go. You can see how the error is being thrown now.
And you can see that if I go ahead and continue something here and now I have a new error, you can see how it replaces that error so that's also something we want we don't want that error to always be shown there. Great so now let's go ahead and see why we are having these little errors here so I believe we can just easily fix that by adding exclamation question marks here to the data like this. Success does not exist on property error. All right, let's go ahead and check that out. Why doesn't it exist?
All right, if it doesn't exist, we probably didn't get around to doing that yet. Yeah, so it doesn't exist yet. We don't throw success anywhere. So for now we can comment this out and I will add to do, add when we add email, sorry, to FA. Yeah, success is going to exist later when we add two factor authentication.
So in the login function, we're gonna throw a success, meaning the two factor code has been sent, right? So that's what we're gonna use this for, but for now, no need for this. Great, so I don't know when we got that error sorry if I didn't catch it earlier I hope it didn't cause you any problems but yeah you can just add a little question mark here great so we just implemented all out inside of our application So just to wrap up this module, you might be asking yourself well, should we really get an error for this? I'm pretty sure I've seen some pages which are able to combine different OAuth providers. Like it's called automatic linking, right?
Why don't we have that? Is it good to throw an error here? Well, you have an answer for that question inside of frequently asked questions. So in the concepts, frequently asked questions here, there is one called when I sign in with another account with the same email address, why are accounts not linked automatically? And in here you have an explanation of why they do that.
So they are obviously thinking about that. And there obviously is a certain risk between doing that, especially with the amount of providers that they support. I believe that between Google and GitHub, there might be no issue, right? They're obviously very reliable providers, but not the same can be said for all of them I guess. I believe there actually is a little key which you can add to manually link an account.
I think it's called something like dangerous linking and then you just enable that. But I would recommend you don't do that. Obviously, if they disabled it, they know something we don't. Great. So you just wrap that up.
Great, great job. So what we're going to do next is we're going to go back to our register here and we're going to implement the email verification for credential users because we technically wrapped up email verification for OAuth users.