How to use NextAuth.JS with Discord
Tags: javascript, nextjs, react, nextauth
Published: 2024-05-13 07:32:33

I was working on a dashboard project recently and had the task of letting Discord users log in to the application and choose one of their servers to modify its properties.

For someone who hadn't implemented authentication with NextJS before, I was clueless. I kept switching between NextAuth, Auth0, Clerk, and Supabase. I abandoned NextAuth in the first few seconds when I experienced an error. 

Being a newbie is difficult. More often than not, these first options are the most ideal. I tried everything and came back to NextAuth. I figured out how to implement it and spent countless hours, so you don't have to!

Let's dive into the article and see how to implement authentication ourselves.

## Discord Provider for NextAuth

NextAuth allows developers to set up authentication for multiple services, like Google, Facebook, Apple, Github, etc. NextAuth calls these services and platforms as providers. In other words, these providers are services, and users can use them to log in to your application.

We will use the Discord Provider for this article. Unlike other providers, Discord requires extra configuration to set up authentication. We must first sort those requirements.

You must create an application on the Discord Developer Portal to access the Discord API and Discord's infrastructure. Then, take the Client ID and the Client Secret. You might need to reset the Client Secret for security purposes.

You can proceed to the next step once you get the necessary credentials. If you cannot get the relevant information, do a basic search on your choice of browser, and someone will guide you.

### NextJS App Initialisation

If you haven't already, you must set up the NextJS application. Use the following command in your terminal to initiate a NextJS application and follow the steps.

```
npx create-next-app@latest
```

For the demonstration, I chose JavaScript over TypeScript (don't be angry at me) and TailwindCSS to demonstrate the authentication features in the front end. Also, I'm using the latest App Router to facilitate the API routes and a better experience overall.

I haven't chosen the `src/` configuration either. We're going to do everything inside the `app/` directory. However, you can follow the same steps and adjust the code snippets for your application.

### Installing NextAuth

You must use the following command to install NextAuth in your project.

```
npm install next-auth
```

After the installation, create a new API route to handle different requests related to authentication. First, create a folder named `api/` inside the `app/` directory. 

Then, add a nested folder inside the `api/` directory and name it `auth/`. Finally, make a directory called `[...nextauth]` inside the `auth/` directory. NextAuth requires this directory structure strictly to do its magic. 

To handle the incoming requests, create a `route.js` file inside the `[...nextauth]` folder. It is the dynamic route handler. Required Path: `app/api/auth/[...nextauth]/route.js`.

![Directory Structure - Afan Khan LLC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jg8jvi8jugwnhdddbwuc.png)

### Creating API Route for NextAuth

The dynamic route handler will store our NextAuth configuration and handle the incoming requests related to NextAuth.

Start by importing NextAuth to the route handler file. Then, import the Discord Provider from NextAuth. After importing them, we will begin configuring NextAuth.

```
import NextAuth from "next-auth/next";
import DiscordProvider from "next-auth/providers/discord";
```

You will need your Client ID and Client Secret now. Keep them handy. Now, you will create an object named AuthOptions. It will be an object with values required for the authentication. 

Remember, you must use the export keyword while initialising the object.

We will use this object to access the current session in server components. By the way, even the session is server-based and not DB-related. Here's how your file should look like.

```
import NextAuth from "next-auth/next";
import DiscordProvider from "next-auth/providers/discord";

export const AuthOptions = {
  providers: [
    DiscordProvider({
      clientId: "CLIENT_SECRET",
      clientSecret:
        "CLIENT_SECRET",
    }),
  ],
};
```

Now, you must initialise the authentication using the NextAuth function. This function requires the AuthOptions and its properties as parameters. Therefore, we will pass the AuthOptions object as a parameter.

```
const handler = NextAuth(AuthOptions);
```

You successfully integrated your configuration options with NextAuth. However, there's more. When someone uses Discord to log in to their account, Discord generates a code for the application to process and access the account of the signed-in user.

We can use this unique code to get a serialized token. A token is a unique string of characters representing the signed-in user and acts as a keycard to access the Discord API. We can only access the Discord API if we have the Client Secret of the bot or the token of a valid Discord user.

You can think of the token as the password to Discord's infrastructure and database but limited to the signed-in user.

Some of you might be wondering where this unique code resides. Discord puts it in the URL where the application redirects the user after a successful sign-in. Here's how NextAuth helps us.

Instead of putting our custom redirection URL, we will take the help of NextAuth. In the OAuth section of the selected application in the Discord Developer Portal, I will insert the following redirection URL, which will redirect the user to the route handler of NextAuth after a successful login.

The redirection URL is the path to the dynamic route handler we had created earlier in our locally stored application. In the production build, you will replace the `localhost:3000` with your domain name or VPS IP.

Once the user logs in, they will get redirected to NextAuth's route handling system, and NextAuth will immediately swap the code for a token and store it.

### NextAuth Callbacks

I want the user to get redirected to a different page once the user logs in. I don't want them to remain on the default NextAuth page or get redirected to the root directory (homepage) by default. Therefore, I'll use a callback by NextAuth to handle the redirection.

NextAuth provides callbacks that developers can configure for their unique use cases. NextAuth callbacks get invoked whenever specific events are triggered. For example, the `signIn()` callback will execute when a user logs in.

In our case, we will override the `redirect()` callback. The callback takes two parameters, but we only need the baseUrl, which is usually the URL of the root directory (`localhost:3000/`).

```
import NextAuth from "next-auth/next";
import DiscordProvider from "next-auth/providers/discord";

export const AuthOptions = {
  providers: [
    DiscordProvider({
      clientId: "YOUR_CLIENT_ID",
      clientSecret:
        "YOUR_CLIENT_SECRET",
    }),
  ],

  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl + "/dashboard";
    },
  },
};

const handler = NextAuth(AuthOptions);
```

You will also export the result of invoking the `NextAuth()` function from the route handler. Otherwise, GET and POST requests to access the sessions, or otherwise, will not work.

Here's what your `route.js` file should look like. You can replace the `/dashboard` with whatever you desire.

```
import NextAuth from "next-auth/next";
import DiscordProvider from "next-auth/providers/discord";

export const AuthOptions = {
  providers: [
    DiscordProvider({
      clientId: "YOUR_CLIENT_ID",
      clientSecret:
        "YOUR_CLIENT_SECRET",
    }),
  ],

  callbacks: {
    async redirect({ url, baseUrl }) {
      return baseUrl + "/dashboard";
    },
  },
};

const handler = NextAuth(AuthOptions);

export { handler as GET, handler as POST };
```

## Discord Permissions

By default, you can only access the username and email of the signed-in user. You can try to use the Discord API and its endpoints to access more information, but Discord will deny the access. It is because Discord boosts privacy and security.

You need the user's permission to access more information. You can take their permission while they are trying to log in. If you have noticed, Discord lists the permissions the user gives to the application when trying to log in.

![Discord Permissions Image - Afan Khan LLC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4e060pqzc9dsylbow5p9.png)

If you thought they were random, you were mistaken. The developer requests these permissions, and users usually overlook them. We can request permissions by adding them to the authorization URL. It's time to go back to the `route.js` file.

In the `DiscordProvider` function, you must add another property named `authorization`. The value of this property will depend on your requirements of the application. As I had stated earlier, I want to access the guilds of the signed-in user and display them.

Therefore, I will ask for that permission, including a few others. You can find them in the Discord Developer Portal. 

![Permission Options - Afan Khan LLC](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bka257pnj680q5rtip77.png)

Every permission name will be connected using the '+' symbol in the URL query params.

```
providers: [
    DiscordProvider({
      clientId: "YOUR_CLIENT_ID",
      clientSecret:
        "YOUR_CLIENT_SECRET",
      authorization:
        "https://discord.com/api/oauth2/authorize?scope=identify+guilds",
    }),
  ],
```

Now, let us try to configure the front end to initiate the login process.

## Configuring the front-end

Since I initiated a new application, I will make modifications in the root directory. If you wish to put the login button elsewhere, you can do so. The steps remain the same.

I will create a client component named `LoginUsingDiscord.jsx` in the root directory to handle the `onClick` events because my main `page.js` file will remain in the default server component mode.

Inside the component, I will import the `signIn()` function from "next-auth/react" and invoke it whenever someone clicks on the "Login" button. We can pass one parameter to the `signIn()` function. It is the name of the provider that the login button represents and should get invoked when clicked.

```
"use client";

import { signIn, signOut } from "next-auth/react";

export default function LoginUsingDiscord() {
  return (
    <section className="flex gap-8 items-center justify-center mt-12">
      <p className="font-bold text-lg">Login Using Discord</p>
      <button
        onClick={() => signIn("discord")}
        className="text-base py-3 px-4 bg-[#5865F2]"
      >
        Login
      </button>
    </section>
  );
}
```

And that's it. Once you click the button, you will get redirected to the Discord login interface. After the serialization of the token, you will get pointed towards your selected URL from the `redirect()` callback. For me, it was `/dashboard`.

After a successful login, you can access the session at `localhost:3000/api/auth/session`. As I explained, this session will only have the username and email by default. I want to store the Discord user ID of the signed-in user and use it globally in the application.

I will override another callback function named signIn() inside route.js. For security purposes, the token isn't accessible elsewhere in the application except in the boundaries of NextAuth.

```
async signIn({ account }) {
  const discordToken = account.access_token;

  return true;
},
```

---

I'm glad you found this article to be helpful. I'm writing an eBook to help people get jobs, earn outsized salaries, etc. It will take me a month or two to complete it. 

However, I've opened the waitlist for people to hop in early and benefit from the early-bird discount and exclusive access to the community to grow a personal brand. You will also meet like-minded people.

Here's the link to sign up. I will not bore you with emails. But if I send an email, it will be worth looking out for.

---

The `signIn()` callback provides the user, account, session and other values as parameters. We only require the account parameter to retrieve the access token. 

In my dashboard project, I stored the token inside the DB after hashing it. However, I won't do the same here. I will try to access the information about the user using the token and print it in the console for simplicity.

You must save the token somewhere to use the Discord API in routes or server components. Every request to the Discord API requires the Discord user token. Since I cannot make this article complex, I will not store the token anywhere and solely display the fetched data in the console.

However, I will guide you on accessing the session on other pages in your application and display information later.

Since I'm not storing the token anywhere, I cannot use it outside the `route.js` file. Therefore, I will make an API request to the Discord API inside the `signIn()` callback and show you how it works.

## Using the Discord API

Let us continue modifying the `signIn()` callback. I will fetch the information about the user and print it to the console for simplicity. The endpoints of these API requests are in the Discord Documentation.

I'm making a GET request to the `api/users/@me` endpoint provided by Discord to get an object with the information of the signed-in user. It doesn't matter whether the user is signed in. Each access token represents a unique user.

The token expires and requires a renewal every month. I make those API requests on the user's behalf whenever I use their token. Discord treats me as a mediator.

```
async signIn({ account }) {
   const discordToken = account.access_token;

   const discordUser = await fetch(`https://discord.com/api/users/@me`, {
      method: "GET",
      headers: {
        Authorization: `Bearer ${discordToken}`,
      },
    }).then((res) => res.json());
  
    console.log(discordUser);

    return true;
},
```

You can pass the access token in the Authorization property of the header and get a response from the API endpoint if you did everything correctly.

![Response from API](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2ojc2kxzao2kz36g711i.png)

I eventually added a sign-out button inside the LoginUsingDiscord component. I used the `signOut()` function. It does not require any parameters because only one service can have an active session. Here's the code.

```
<button
  onClick={() => signOut()} className="text-base py-3 px-4 bg-[#ED4245]">
  Sign Out
</button>
```

## Conclusion

Authentication is challenging to implement in most applications, and NextAuth makes it easier for us to do so. You learned how to create a session, allow users to log in using Discord, use the token to send API requests to the Discord API, etc.

In the same way, you can use other providers, such as Google. I hope this article proved helpful to you. If you want to get a job as a software engineer, I suggest you join the waitlist.

If you have any doubts, reach out to _hello@afankhan.com_ or _@whyafan_ elsewhere.