Implementing OAuth 2.0 in the Next-13 app using next-auth

Raazesh Prajapati
readytowork, Inc.
Published in
13 min readSep 20, 2023

OAuth 2.0, which stands for “Open Authorization 2.0,” is an industry-standard protocol for authorization and authentication. It allows third-party applications to access user data without exposing the user’s credentials.

Next.js is a popular React framework for building web applications, and NextAuth.js is an authentication library that can be used with Next.js to implement OAuth2 authentication and other authentication strategies. Here’s a brief overview of how we can set up OAuth2 authentication using NextAuth.js in a Next.js application.

💻 Implementing OAuth 2.0 using next-auth in Next-app:

In this article, we will guide you through the process of implementing OAuth2 using next-auth in a Next13 app.

🌟Getting Started

Before starting our project we should have the following prerequisites in our local system.

📦Prerequisites:

👷 Creating and setting up Next-js project:

We are using create-next-app, which sets up everything automatically for us. We want to start with a TypeScript in our project so we should use the --typescript flag. To create a project, run:

# I am using yarn as package manager
yarn create next-app --typescript
# or
npx create-next-app@latest --typescript
# or
pnpm create next-app --typescript

For more about NextJs application, you can visit next-js official documentation here.

During the process, you will be prompted to choose which features to enable for the project. Make sure to select “Yes” for TypeScript and ESLint. Additionally, you will be asked if you would like to use the src/ directory and the experimental app/ directory. I have chosen “Yes” for src/ directory options and “Yes” for app/ directory options. For the import alias, simply press Tab and Enter to accept.

🎉 Bravo, our basic next-app setup has been completed.

💠 Install NextAuth.js:

We can install NextAuth.js using npm or yarn:

npm install next-auth
# or
yarn add next-auth

⚙️ Configure NextAuth.js:

In light of observing the project in action, let’s delve into the process of implementing Google and GitHub OAuth using NextAuth. This involves a straightforward approach of incorporating the relevant providers into the NextAuth configuration options and ensuring that the essential client IDs and secrets are supplied.

To initiate this process, commence by creating a fresh folder titled lib within the src directory of our project. Within this newly created lib folder, establish an auth.ts file, and within it, insert the prescribed NextAuth configurations as outlined below. This separation of concerns will help streamline the setup and enhance the maintainability of our project’s authentication mechanism.

src/lib/auth.ts

import type { NextAuthOptions } from "next-auth";
import GitHubProvider from "next-auth/providers/github";
import GoogleProvider from "next-auth/providers/google";

// Define authentication options using NextAuthOptions interface
export const authOptions: NextAuthOptions = {
// Customize authentication pages
pages: {
signIn: "/login", // Redirect users to "/login" when signing in
},
// Configure session management
session: {
strategy: "jwt", // Use JSON Web Tokens (JWT) for session management
},
// added secret key
secret: process.env.NEXT_PUBLIC_SECRET,
// Configure authentication providers
providers: [
GoogleProvider({
// Configure Google authentication provider with environment variables
clientId: process.env.GOOGLE_CLIENT_ID as string,
clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
}),
GitHubProvider({
// Configure GitHub authentication provider with environment variables
clientId: process.env.GITHUB_ID as string,
clientSecret: process.env.GITHUB_SECRET as string,
}),
// CredentialsProvider({}), // Include a Credentials provider (username/password)
],
};

The provided code snippet above contains crucial references to the OAuth client IDs and secrets for both the Google and GitHub providers. To effectively utilize these providers within your project, it is imperative to supply the corresponding client IDs and secrets via your project’s .env file. If you find yourself uncertain about how to obtain these values, there’s no need for concern. Toward the end of this article, you’ll discover two comprehensive sections that walk you through the step-by-step process of obtaining the necessary Google and GitHub OAuth client IDs and secrets.

With the configuration of OAuth providers and the inclusion of the respective IDs and secrets now addressed, let’s take a moment to examine the state of your api/auth/[…nextauth]/route.ts file. As you’ve progressed through the setup, your route file should have transformed into the structure depicted below, reflecting the integration of Google and GitHub OAuth authentication mechanisms. This configuration will pave the way for secure and efficient user authentication within your project.

src/app/api/auth/[…nextauth]/route.ts

import { authOptions } from "@/lib/auth";
import NextAuth from "next-auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

🗃 Implement the Google and GitHub OAuth2:

Having successfully incorporated the Google and GitHub OAuth providers into the NextAuthOptions object, the subsequent phase involves implementing the necessary logic for their utilization within the front-end application. Remarkably, this process is quite straightforward in nature. To make it happen seamlessly, you’ll primarily focus on enhancing the user experience by integrating Google and GitHub OAuth buttons into your login form. When a user interacts with these buttons by clicking on them, you can efficiently facilitate the authentication process by invoking the signIn() function. As its first argument, you’ll simply pass in the respective provider type, ensuring a smooth and intuitive login experience for your users.

Create the Login Form:

In the context of Next.js 13.2, a notable feature is the automatic rendering of components located in the app directory as React Server Components. However, if you intend to use the onClick={} DOM attribute, which is specific to the browser environment, you must include the “use client;” directive at the outset of the file. This directive serves as a signal to Next.js, instructing it to render the form exclusively in the browser, thereby granting you full access to all the browser APIs.

By default, NextAuth augments the URL with a callbackUrl parameter following user sign-out. This parameter facilitates redirecting the user to their previous page during the subsequent sign-in. To ensure the user is redirected accurately after signing in, it’s advisable to verify the presence of this parameter in the URL and subsequently pass it to the signIn() method.

For a comprehensive overview of how all the integral components of authentication are seamlessly assembled, including the flexibility to employ either the credential provider or opt for Google or GitHub OAuth, you can reference the form.tsx component provided below. This component embodies the core elements required for a robust and versatile authentication system within your project.

src/app/login/form.tsx

"use client"; // Indicates that this module is client-side code.

import { signIn } from "next-auth/react"; // Import the signIn function from NextAuth for authentication.
import { useSearchParams, useRouter } from "next/navigation"; // Import Next.js navigation utilities.
import { ChangeEvent, useState } from "react"; // Import React hooks for managing component state.

export const LoginForm = () => {
const router = useRouter(); // Initialize the Next.js router.
const [loading, setLoading] = useState(false); // State for managing loading state.
const [formValues, setFormValues] = useState({
email: "",
password: "",
}); // State for form input values.
const [error, setError] = useState(""); // State for handling errors during authentication.

const searchParams = useSearchParams(); // Get query parameters from the URL.
const callbackUrl = searchParams.get("callbackUrl") || "/profile"; // Define a callback URL or use a default one.

// Handle form submission
const onSubmit = async (e: React.FormEvent) => {
e.preventDefault(); // Prevent the default form submission behavior.
try {
setLoading(true); // Set loading state to true.
setFormValues({ email: "", password: "" }); // Clear form input values.

// Attempt to sign in using the credentials (email and password).
const res = await signIn("credentials", {
redirect: false,
email: formValues.email,
password: formValues.password,
callbackUrl,
});

setLoading(false); // Set loading state back to false.

console.log(res); // Log the authentication response.
if (!res?.error) {
router.push(callbackUrl); // Redirect to the callback URL on successful authentication.
} else {
setError("invalid email or password"); // Set an error message for invalid credentials.
}
} catch (error: any) {
setLoading(false); // Set loading state back to false on error.
setError(error); // Set the error message for any other errors.
}
};

// Handle input field changes
const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
const { name, value } = event.target;
setFormValues({ ...formValues, [name]: value }); // Update the form input values.
};

// Define a CSS class for form inputs.
const input_style =
"form-control block w-full px-4 py-5 text-sm font-normal text-gray-700 bg-white bg-clip-padding border border-solid border-gray-300 rounded transition ease-in-out m-0 focus:text-gray-700 focus:bg-white focus:border-blue-600 focus:outline-none";

return (
<form onSubmit={onSubmit}>
{error && (
<p className="text-center bg-red-300 py-4 mb-6 rounded">{error}</p>
)}

{/* Email input field */}
<div className="mb-6">
<input
required
type="email"
name="email"
value={formValues.email}
onChange={handleChange}
placeholder="Email address"
className={`${input_style}`}
/>
</div>

{/* Password input field */}
<div className="mb-6">
<input
required
type="password"
name="password"
value={formValues.password}
onChange={handleChange}
placeholder="Password"
className={`${input_style}`}
/>
</div>

{/* Sign In button */}
<button
type="submit"
style={{ backgroundColor: `${loading ? "#ccc" : "#3446eb"}` }}
className="inline-block px-7 py-4 bg-blue-600 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:bg-blue-700 hover:shadow-lg focus:bg-blue-700 focus:shadow-lg focus:outline-none focus:ring-0 active:bg-blue-800 active:shadow-lg transition duration-150 ease-in-out w-full"
disabled={loading}
>
{loading ? "loading..." : "Sign In"}
</button>

{/* OR divider */}
<div className="flex items-center my-4 before:flex-1 before:border-t before:border-gray-300 before:mt-0.5 after:flex-1 after:border-t after:border-gray-300 after:mt-0.5">
<p className="text-center font-semibold mx-4 mb-0">OR</p>
</div>

{/* Sign In with Google button */}
<a
className="px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center mb-3"
style={{ backgroundColor: "#ffffff", color: "gray" }}
onClick={() => signIn("google", { callbackUrl })}
role="button"
>
<img
className="pr-2"
src="/images/google.svg"
alt=""
style={{ height: "2rem" }}
/>
Continue with Google
</a>

{/* Sign In with GitHub button */}
<a
className="px-7 py-2 text-white font-medium text-sm leading-snug uppercase rounded shadow-md hover:shadow-lg focus:shadow-lg focus:outline-none focus:ring-0 active:shadow-lg transition duration-150 ease-in-out w-full flex justify-center items-center"
style={{ backgroundColor: "#000000" }}
onClick={() => signIn("github", { callbackUrl })}
role="button"
>
<img
className="pr-2"
src="/images/github.png"
alt=""
style={{ height: "2.2rem" }}
/>
Continue with GitHub
</a>
</form>
);
};

Now that you’ve successfully crafted the login form component, the next step in the process is to integrate it into a page component. Notably, the page component operates as a React Server Component. However, this should pose no complications, thanks to the strategic inclusion of the “use client;” directive at the beginning of the form component. By including this directive, we’ve explicitly communicated to the system that the form component should exclusively render in the browser, ensuring a harmonious and functional integration within the page component. This approach allows for a seamless user experience while making the most of the capabilities offered by React Server Components

src/app/login/page.tsx

import { LoginForm } from "./form";

export default function LoginPage() {
return (
<>
<section className="bg-ct-blue-600 min-h-screen pt-20">
<div className="container mx-auto px-6 py-12 h-full flex justify-center items-center">
<div className="md:w-8/12 lg:w-5/12 bg-white px-8 py-10">
<LoginForm />
</div>
</div>
</section>
</>
);
}

Creating a Profile page to redirect after successful login:

src/app/profile/form.tsx

import { getServerSession } from "next-auth";
import { authOptions } from "@/lib/auth";
import Header from "@/compponents/header.component";

export default async function Profile() {
const session = await getServerSession(authOptions);
const user = session?.user;

return (
<>
<Header />
<section className="bg-ct-blue-600 min-h-screen pt-20">
<div className="max-w-4xl mx-auto bg-ct-dark-100 rounded-md h-[20rem] flex justify-center items-center">
<div>
<p className="mb-3 text-5xl text-center font-semibold">
Profile Page
</p>
{!user ? (
<p>Loading...</p>
) : (
<div className="flex items-center gap-8">
<div>
<img
src={user.image ? user.image : "/images/default.png"}
className="max-h-36"
alt={`profile photo of ${user.name}`}
/>
</div>
<div className="mt-8">
<p className="mb-3">Name: {user.name}</p>
<p className="mb-3">Email: {user.email}</p>
</div>
</div>
)}
</div>
</div>
</section>
</>
);
}

The above code represents a user profile page that fetches user session information, displays a header, and shows user details (including an image, name, and email) once the session is loaded. It uses utility classes for styling, which suggests the use of a CSS framework or library like Tailwind CSS.

⚙️ How to Generate the Google OAuth2 Credentials:

Generating Google OAuth2 credentials involves a few steps. These credentials are typically required when you want to enable secure access to Google APIs or services. Here’s a general overview of the process:

  1. Go to the Google Cloud Console:

2. Create a Project (if necessary):

  • If you don’t have an existing project, create one by clicking the project name at the top of the console and then selecting “New Project.” Follow the prompts to create a new project.

3. Once your project is created, click the “SELECT PROJECT” button from the notifications.

4. Click the “OAuth consent screen” menu on the left sidebar. Choose “External” as the “User Type” and click on the “CREATE” button.

5. On the “Edit app registration” screen, go to the “App information” section and fill in the required details, including a logo for the consent screen.

Under the “App domain” section, provide links to your homepage, privacy policy, and terms of service pages. Input your email address under the “Developer contact information” section and click on the “SAVE AND CONTINUE” button.

6. On the “Scopes” screen, click on the “ADD OR REMOVE SCOPES” button, select .../auth/userinfo.email and .../auth/userinfo.profile from the list of options, and then click on the “UPDATE” button. Scroll down and click the “SAVE AND CONTINUE” button.

7. On the “Test users” screen, add the email addresses of Google accounts that will be authorized to test your application while it is in sandbox mode. Click the “ADD USERS” button and input the email addresses. Click the “SAVE AND CONTINUE” button to proceed.

8. Click on the “Credentials” option in the left sidebar. Select the “CREATE CREDENTIALS” button and choose “OAuth client ID” from the list of options provided.

9. Choose “Web application” as the application type, and input a name for your app. Specify the authorized redirect URI as http://localhost:3000/api/auth/callback/google and click the “Create” button.

Remember that OAuth2 credentials should be kept confidential and should not be hard-coded in your application’s source code. Instead, use a secure method to store and retrieve them.

Once we have generated the client ID, proceed by copying both the client ID and secret from the “Credentials” page. Paste these values into your .env file, ensuring that we use the exact keys that NextAuth requires for these credentials. To assist you, here is an illustrative example of what our.env file should look like:

.env

NEXTAUTH_SECRET=secure_nextauth_secret
NEXTAUTH_URL=http://localhost:3000
GITHUB_ID=
GITHUB_SECRET=
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

⚙️ How to Generate the GitHub OAuth Credentials:

To generate GitHub OAuth credentials, follow these steps:

  1. Sign in to GitHub: If you don’t have a GitHub account, you’ll need to create one. If you already have an account, sign in to GitHub.
  2. Go to Developer Settings: Once we’re signed in, click on your profile picture in the top right corner, then select “Settings.” In the left sidebar, click on “Developer settings.

3. OAuth Apps: In the Developer settings menu, click on “OAuth Apps” under the “Access” section.

4. New OAuth App: Click the “New OAuth App” button.

5. Fill in App Details:

  • Application name: Choose a name for your OAuth application.
  • Homepage URL: This can be your project’s website or any relevant URL.
  • Application description: Provide a brief description of your application.
  • Authorization callback URL: This is the URL where users will be redirected after authorizing your application. Enter the callback URL you intend to use.

6. Access Permissions: Under “Select scopes,” choose the permissions we want our OAuth app to have. These permissions determine what actions our app can perform on the user’s behalf.

7. Click “Register application”: Once we’ve filled in the details and selected permissions, click the “Register application” button.

8. Client ID and Client Secret: After registering our application, we’ll be provided with a “Client ID” and a “Client Secret.” These are our OAuth credentials. Keep them confidential, as client secret should not be exposed in client-side code.

That’s it! We now have our GitHub OAuth credentials. We can use these credentials in our application to authenticate users and access GitHub resources.
After generating the GitHub OAuth client secret, you should proceed by adding it, along with the client ID, to our .env file. It’s important to note that NextAuth requires specific keys for these credentials. You can refer to the following example .env file as a guide:

.env

NEXTAUTH_SECRET=secure_nextauth_secret
NEXTAUTH_URL=http://localhost:3000

GITHUB_ID=
GITHUB_SECRET=

GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=

🏁 Conclusion

In conclusion, we have successfully walked through the steps of integrating Google and GitHub OAuth authentication into a Next.js application using NextAuth. We have also discussed the crucial steps involved in obtaining the necessary OAuth client IDs and secrets from both Google and GitHub. By following this tutorial, you should now have a solid foundation for implementing OAuth authentication in your Next.js projects with ease.

Please note that the specific configuration and setup may vary depending on the OAuth2 provider we are using (e.g., Google, Facebook, GitHub, etc.). Be sure to refer to the NextAuth.js documentation and the documentation of our chosen OAuth2 provider for detailed instructions on setting up OAuth2 authentication in our Next.js application.

Repo Link:https://github.com/RaazeshP96/oauth_nextjs

Demo URL:https://oauth-nextjs-tau.vercel.app/login

--

--