Step-by-Step Guide: Building a Next.js App with Google Authentication and Credentials,Next Auth , MongoDB Atlas, and Mongoose.

Nithish Reddy
10 min readSep 9, 2023

--

For a foundational guide on setting up Next.js with MongoDB, be sure to check out this informative article at https://medium.com/@nithishreddy0627/connecting-your-next-js-project-to-mongodb-atlas-using-mongoose-a-step-by-step-guide-2d2552b5d7ca

If you’re looking for the complete project, you can find it uploaded on GitHub at https://github.com/nithishreddy27/authentication-google-credientails . For a detailed, step-by-step guide on how to set up and configure this project, please refer to the article.

I hope you have basic setup of Nextjs with mongodb Atlas.

Step 1: Begin by installing the necessary packages using the command

npm install next-auth axios crypto.

Step 2: Access the Google Cloud Console at https://cloud.google.com/cloud-console. Log in to your Google Cloud account, and then click on the console button located in the upper-right corner.

Choose Console

Step 3: Next, navigate to the top left corner and click on “Projects” in the Google Cloud Console. Then, select “New Project,” provide your desired project name, and click “Create” to initiate the project creation process.

After creating the project, you can now choose the project you just created from the list of projects displayed in the top-left corner.

Now, in the search bar located at the top, type “OAuth clients” and click on “Credentials.”

After that, navigate to “OAuth consent screen,” where you’ll need to provide basic details such as your app’s name and email address.

Choose external and click on create.

The app logo and domain are optional, so you can leave those fields empty if you prefer. Scroll down, add your email address in the “Developer contact information” section, and then click on “Save and Continue.”

In the next step, keep the scopes as they are and proceed by clicking on “Save and Continue.”

Under “Test users,” click on “Add user,” then enter your email address. Afterward, click on “Save and Continue.”

Next go to Credentials Tab and click on create Credentials and select OAuth Client ID.

Now select Web Application from Application type dropdown.

Now add Authorised JavaScript origins as
http://localhost:3000

and Authorised redirect URLs as http://localhost:3000/api/auth/callback/google

and click on create.

In the small window, you will find your Client ID and Client Secret.

Copy both of them. Now, paste these credentials into your .env file in your Next.js project, using the variable names GOOGLE_CLIENT_ID for the Client ID and GOOGLE_CLIENT_SECRET for the Client Secret.

.env file contains GOOGLE_CLIENT_ID , GOOGLE_CLIENT_SECRET , DATABASE_URL

Step 4 : Open the _app.js file, which is located in the pages directory of your Next.js project, and copy-paste the following code.

import '@/styles/globals.css'
import { SessionProvider } from "next-auth/react"

export default function App({ Component, pageProps: { session, ...pageProps } }) {
return (
<SessionProvider session={session}>

<Component {...pageProps} />
</SessionProvider>
)
}

Step 6 :Create a folder named “Model” outside the “pages” directory in your project. Inside the “Model” folder, create a file named “User.js” and paste the provided code.

import mongoose from "mongoose";

const userSchema = new mongoose.Schema(
{
email: {
type: String,
trim: true,
unique: true
},

hash:{
type:String
},
salt:{
type:String
},
profile: {
firstName: {
type: String,
trim: true,
},
lastName: {
type: String,
trim: true,
},
image: {
type: String,
trim: true,
},

}



},
{ timestamps: true }
);

export default mongoose.models.users || mongoose.model("users", userSchema);

Step 7: Inside your project’s “pages/api/auth” directory, create a file named “[…nextauth.js]” and paste the provided code.

import NextAuth from "next-auth"
import GithubProvider from "next-auth/providers/github"
import GoogleProvider from "next-auth/providers/google";
import CredentialsProvider from "next-auth/providers/credentials";
import crypto from "crypto";
import connectDB from "@/pages/lib/connectDB";
import User from "@/Model/User";

export const authOptions = {
// Configure one or more authentication providers
providers: [
GithubProvider({
clientId: process.env.GITHUB_ID,
clientSecret: process.env.GITHUB_SECRET,
}),
GoogleProvider({
clientId: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,

}),
CredentialsProvider({
// The name to display on the sign in form (e.g. "Sign in with...")
name: "Credentials",
// `credentials` is used to generate a form on the sign in page.
// You can specify which fields should be submitted, by adding keys to the `credentials` object.
// e.g. domain, username, password, 2FA token, etc.
// You can pass any HTML attribute to the <input> tag through the object.
credentials: {
username: { label: "Username", type: "text", placeholder: "jsmith" },
password: { label: "Password", type: "password" }
},
authorize: async (credentials) => {
const { username, password } = credentials;
await connectDB()
const user = await User.findOne({ email: username });
console.log("username",user)

if (!user) {
return Promise.resolve(null);
}

const inputHash = crypto.pbkdf2Sync(password, user.salt, 1000, 64, "sha512").toString("hex");
if(inputHash == user.hash){

return Promise.resolve(user);
}
else{
return Promise.resolve("invalid user");
}
},
}),
// ...add more providers here
],

callbacks: {
async jwt({ token, account }) {
// Persist the OAuth access_token to the token right after signin
if (account) {
token.accessToken = account.access_token
}
// console.log("in jwt",token)
return token
},
async session({ session, token, user }) {

// Send properties to the client, like an access_token from a provider.
session.accessToken = token.accessToken

return session
},
async signIn({user}) {
console.log("inside callback")
await connectDB()
console.log("connected",user)
const u = await User.findOne({email:user.email})
console.log("found",u)
const email = user.email;
const name = user.name;
if(!u){
const newUser = new User({
email,
profile:{
firstName:name
}
})
await newUser.save();
}
return true
}

}


}

export default NextAuth(authOptions)

Step 8: Create a file inside the “pages/auth” directory and name it “signup.js”. Copy and paste the provided code into this file.

import React, { useEffect, useState } from "react";
import { useSession, signIn, signOut } from "next-auth/react";
import { useRouter } from "next/dist/client/router";
import axios from "axios";
import Link from "next/link";

export default function signup() {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [firstName, setFirstName] = useState("");
const [lastName, setLastName] = useState("");
const [confirmPassword, setConfirmPassword] = useState("");
const [error, setError] = useState("");
const { data: session } = useSession();

const handleSubmit = async (e) => {
e.preventDefault();
if (confirmPassword == password) {
await axios.post("../api/auth/signup", {
username,
password,
firstName,
lastName,
});

await signIn("credentials", {
redirect: false,
username: username,
password,
});
} else {
setError("password and confirm password does not match");
}
};

return (
<div className="min-h-screen ">
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Create a account
</h2>
</div>
<form className="mt-8 space-y-6" onSubmit={handleSubmit}>
<input type="hidden" name="remember" value="true" />
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="firstName" className="sr-only">
First Name
</label>
<input
id="firstName"
name="firstName"
type="text"
// autoComplete="email"
required
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="First Name"
/>
</div>
<div>
<label htmlFor="lastName" className="sr-only">
Email address
</label>
<input
id="lastName"
name="lastName"
type="text"
// autoComplete="email"
required
value={lastName}
onChange={(e) => setLastName(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Last Name"
/>
</div>
<div>
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
id="email"
name="Username"
type="email"
autoComplete="email"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Email address"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password"
/>
</div>
<div>
<label htmlFor="confirmPassword" className="sr-only">
Confirm Password
</label>
<input
id="confirmPassword"
name="confirmPassword"
type="password"
autoComplete="current-password"
required
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Confirm Password"
/>
</div>
</div>
<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Sign Up
</button>
</div>
<p className="text-red-500">{error}</p>
<button
onClick={() => signIn("google")}
className="bg-green-600 text-white rounded p-2 "
>
Sign in google
</button>
<button
onClick={() => signOut()}
className="bg-red-600 text-white rounded p-2 mx-10 "
>
Sign out
</button>{" "}
</form>
<Link href="/auth/LoginPage" className="underline ">
Already a user ? Login{" "}
</Link>

<div className="mx-10">
User Details :{session && <div>{JSON.stringify(session)}</div>}
{!session && <div>Not logged in</div>}
</div>
</div>
</div>
</div>
);
}

Step 9: Inside your project’s “pages/api/auth” directory, create a file named “signup.js” and paste the provided code into this file.



import User from "@/Model/User";
import connectDB from "@/pages/lib/connectDB";
import crypto from "crypto";

export default async (req, res) => {
await connectDB()
if (req.method === 'POST') {


const { username,
password,
firstName,
lastName } = req.body;
console.log(username,password,firstName,lastName)
const email = username
const existingUser = await User.findOne({ email });
const salt = crypto.randomBytes(16).toString("hex");
const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, "sha512").toString("hex");
if (existingUser) {
return res.status(400).json({ message: 'User already exists' });
}
else{
const newUser = new User({
email,
hash ,
salt,
profile:{
firstName,
lastName}
});
await newUser.save();
}

}
return res.status(200).send({done:true})
};

Please ensure all the pages imported correctly .

Now can signup either using email and password or using google authentication.

Step 10: To handle the login part, create a file in the “pages/auth” directory named “login.js” and paste the provided code into this file.

// pages/login.js
// pages/login.js

import { useState } from 'react';
import { useRouter } from 'next/router';
import { signIn, useSession,signOut, getSession } from 'next-auth/react';
import Link from 'next/link';

const LoginPage = () => {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [error, seterror] = useState('')
const router = useRouter();
const { data: session } = useSession();

const handleSubmit = async (e) => {
e.preventDefault();
// console.log("e",email,password);
// Use NextAuth.js signIn to authenticate the user
const result = await signIn('credentials', {
redirect: false, // Prevent NextAuth.js from automatically redirecting
username,
password,
});

if (result?.error) {
console.error('Login error:', result.error);
return;
}

console.log(result);
if(!result?.user){
seterror("Invalid username and password")
}

};

return (
<div className="min-h-screen flex items-center justify-center bg-gray-50 py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
<div>
<h2 className="mt-6 text-center text-3xl font-extrabold text-gray-900">
Log in to your account
</h2>
</div>
<form className="mt-8 space-y-6 mb-5" onSubmit={handleSubmit}>
<input type="hidden" name="remember" value="true" />
<div className="rounded-md shadow-sm -space-y-px">
<div>
<label htmlFor="email" className="sr-only">
Email address
</label>
<input
id="email"
name="Username"
type="email"
autoComplete="email"
required
value={username}
onChange={(e) => setUsername(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-t-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Email address"
/>
</div>
<div>
<label htmlFor="password" className="sr-only">
Password
</label>
<input
id="password"
name="password"
type="password"
autoComplete="current-password"
required
value={password}
onChange={(e) => setPassword(e.target.value)}
className="appearance-none rounded-none relative block w-full px-3 py-2 border border-gray-300 placeholder-gray-500 text-gray-900 rounded-b-md focus:outline-none focus:ring-indigo-500 focus:border-indigo-500 focus:z-10 sm:text-sm"
placeholder="Password"
/>
</div>
</div>

<div className="flex items-center justify-between">
<div className="flex items-center">
{/* Add "remember me" checkbox if needed */}
</div>

<div className="text-sm">
{/* Add "Forgot your password?" link if needed */}
</div>
</div>

<div>
<button
type="submit"
className="group relative w-full flex justify-center py-2 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-indigo-600 hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500"
>
Log In
</button>

</div>

<button
onClick={() => signIn("google")}
className="bg-green-600 text-white rounded p-2 "
>
Sign in google
</button>
<button
onClick={() => signOut()}
className="bg-red-600 text-white rounded p-2 mx-10 "
>
Sign out
</button> </form>
<Link href="/auth/signup" className='underline'> New user ? Sign Up</Link>

<div>User : {JSON.stringify(session)}</div>
</div>
</div>
);
};

export default LoginPage;


export async function getServerSideProps(context) {
// Fetch data from external API
const r = await getSession(context)
console.log("inside server",r)
return { props: { } }
}

Now, you have the option to log in using both your credentials and Google authentication services.

In conclusion, this comprehensive guide has walked you through the process of setting up user authentication in your Next.js application, offering two convenient methods for users to sign up and log in. With the ability to use their credentials or the seamless Google authentication service, your application now provides a flexible and secure user experience. By following these steps, you’ve successfully empowered your application with robust user authentication capabilities, enhancing its usability and accessibility.

Thank You

--

--