├── .eslintrc.json
├── src
├── app
│ ├── favicon.ico
│ ├── signup
│ │ └── page.tsx
│ ├── login
│ │ └── page.tsx
│ ├── api
│ │ └── users
│ │ │ ├── me
│ │ │ └── route.ts
│ │ │ ├── logout
│ │ │ └── route.ts
│ │ │ ├── verifyemail
│ │ │ └── route.ts
│ │ │ ├── signup
│ │ │ └── route.ts
│ │ │ └── login
│ │ │ └── route.ts
│ ├── layout.tsx
│ ├── globals.css
│ └── page.tsx
├── helpers
│ ├── getDataFromToken.ts
│ └── mailer.ts
├── dbConfig
│ └── dbConfig.ts
└── models
│ └── userModel.js
├── next.config.mjs
├── postcss.config.js
├── .env.sample
├── .gitignore
├── tailwind.config.ts
├── public
├── vercel.svg
└── next.svg
├── tsconfig.json
├── README.md
└── package.json
/.eslintrc.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": ["next/babel","next/core-web-vitals"]
3 | }
--------------------------------------------------------------------------------
/src/app/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vishalparmarr/nextjsauth/HEAD/src/app/favicon.ico
--------------------------------------------------------------------------------
/next.config.mjs:
--------------------------------------------------------------------------------
1 | /** @type {import('next').NextConfig} */
2 | const nextConfig = {};
3 |
4 | export default nextConfig;
5 |
--------------------------------------------------------------------------------
/postcss.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | plugins: {
3 | tailwindcss: {},
4 | autoprefixer: {},
5 | },
6 | };
7 |
--------------------------------------------------------------------------------
/src/app/signup/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const page = () => {
4 | return (
5 |
page
6 | )
7 | }
8 |
9 | export default page
--------------------------------------------------------------------------------
/src/app/login/page.tsx:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | const page = () => {
4 | return (
5 |
6 |
7 |
8 | )
9 | }
10 |
11 | export default page
12 |
--------------------------------------------------------------------------------
/.env.sample:
--------------------------------------------------------------------------------
1 | MONGO_URI=mongodb+srv://YOUR_USERNAME:YOUR_PASSWORD@nextjsauth.nqk0shz.mongodb.net/
2 | TOKEN_SECRET="chai aur code"
3 | DOMAIN=https://localhost:3000
4 | USER=mailtrap.email
5 | PASS=mailtrap.password
--------------------------------------------------------------------------------
/src/helpers/getDataFromToken.ts:
--------------------------------------------------------------------------------
1 | import { NextRequest } from "next/server";
2 | import jwt from "jsonwebtoken";
3 | import { request } from "http";
4 |
5 | export const getDataFromToken = (request: NextRequest) => {
6 | try {
7 | const token = request.cookies.get("token")?.value || "";
8 | const decodedToken:any = jwt.verify(token, process.env.TOKEN_SECRET!);
9 | return decodedToken.id;
10 |
11 | } catch (error: any) {
12 | throw new Error(error.message);
13 | }
14 | };
15 |
--------------------------------------------------------------------------------
/src/app/api/users/me/route.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "@/dbConfig/dbConfig";
2 | import User from "@/models/userModel";
3 | import { NextRequest, NextResponse } from "next/server";
4 | import { getDataFromToken } from "@/helpers/getDataFromToken";
5 |
6 | connect();
7 |
8 | export async function POST(request: NextRequest) {
9 |
10 | const userId = await getDataFromToken(request);
11 | const user = User.findOne({_id: userId}).select("-password");
12 |
13 | return NextResponse.json({
14 | message: "User found",
15 | data: user
16 | });
17 | }
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 | .yarn/install-state.gz
8 |
9 | # testing
10 | /coverage
11 |
12 | # next.js
13 | /.next/
14 | /out/
15 |
16 | # production
17 | /build
18 |
19 | # misc
20 | .DS_Store
21 | *.pem
22 |
23 | # debug
24 | npm-debug.log*
25 | yarn-debug.log*
26 | yarn-error.log*
27 |
28 | # local env files
29 | .env*.local
30 | .env
31 |
32 |
33 | # vercel
34 | .vercel
35 |
36 | # typescript
37 | *.tsbuildinfo
38 | next-env.d.ts
39 |
--------------------------------------------------------------------------------
/src/app/layout.tsx:
--------------------------------------------------------------------------------
1 | import type { Metadata } from "next";
2 | import { Inter } from "next/font/google";
3 | import "./globals.css";
4 |
5 | const inter = Inter({ subsets: ["latin"] });
6 |
7 | export const metadata: Metadata = {
8 | title: "Create Next App",
9 | description: "Generated by create next app",
10 | };
11 |
12 | export default function RootLayout({
13 | children,
14 | }: Readonly<{
15 | children: React.ReactNode;
16 | }>) {
17 | return (
18 |
19 | {children}
20 |
21 | );
22 | }
23 |
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | const config: Config = {
4 | content: [
5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
8 | ],
9 | theme: {
10 | extend: {
11 | backgroundImage: {
12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))",
13 | "gradient-conic":
14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))",
15 | },
16 | },
17 | },
18 | plugins: [],
19 | };
20 | export default config;
21 |
--------------------------------------------------------------------------------
/public/vercel.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/api/users/logout/route.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "@/dbConfig/dbConfig";
2 | import { NextRequest, NextResponse } from "next/server";
3 |
4 | connect();
5 |
6 | export async function GET(request: NextRequest) {
7 | try {
8 | const response = NextResponse.json({
9 | message: "Logout Successfully",
10 | success: true,
11 | });
12 |
13 | response.cookies.set("token", "", {
14 | httpOnly: true,
15 | expires: new Date(0),
16 | });
17 |
18 | return response;
19 | } catch (error: any) {
20 | return NextResponse.json({ error: error.message }, { status: 500 });
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/dbConfig/dbConfig.ts:
--------------------------------------------------------------------------------
1 | import mongoose from "mongoose";
2 |
3 | export async function connect() {
4 |
5 | try {
6 | mongoose.connect(process.env.MONGODB_URI!);
7 | const connection = mongoose.connection;
8 | connection.once('connected', () => {
9 | console.log("Connected to the database");
10 | })
11 |
12 | connection.on('error', (err) => {
13 | console.log("Error while connecting to the database " + err);
14 | process.exit();
15 | })
16 | }
17 | catch (error) {
18 | console.log("Error while connecting to the database " + error);
19 | }
20 | }
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "lib": ["dom", "dom.iterable", "esnext"],
4 | "allowJs": true,
5 | "skipLibCheck": true,
6 | "strict": true,
7 | "noEmit": true,
8 | "esModuleInterop": true,
9 | "module": "esnext",
10 | "moduleResolution": "bundler",
11 | "resolveJsonModule": true,
12 | "isolatedModules": true,
13 | "jsx": "preserve",
14 | "incremental": true,
15 | "plugins": [
16 | {
17 | "name": "next"
18 | }
19 | ],
20 | "paths": {
21 | "@/*": ["./src/*"]
22 | }
23 | },
24 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
25 | "exclude": ["node_modules"]
26 | }
27 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
NEXT JS AUTH
3 |
4 |
5 | This repository is based on the production level authentication system using Next.js by [Chai aur Code](https://www.youtube.com/@chaiaurcode)
6 | Youtube Channel.
7 |
8 | Thanks [Hitesh Choudhary](https://github.com/hiteshchoudhary) sir for creating a course..
9 |
10 | ## Getting Started
11 | - Clone the Repository
12 |
13 | ```bash
14 | git clone https://github.com/vishalparmarr/nextjsauth.git
15 | cd nextjsauth
16 | ```
17 | - Install Dependencies
18 |
19 | ```bash
20 | npm install
21 | ```
22 | - Change the env files
23 | ``` bash
24 | .env.sample to .env
25 | and add the environment variables
26 | ```
27 |
28 | - Run the Server
29 |
30 | ```bash
31 | npm run dev
32 | ```
33 |
--------------------------------------------------------------------------------
/src/app/globals.css:
--------------------------------------------------------------------------------
1 | @tailwind base;
2 | @tailwind components;
3 | @tailwind utilities;
4 |
5 | :root {
6 | --foreground-rgb: 0, 0, 0;
7 | --background-start-rgb: 214, 219, 220;
8 | --background-end-rgb: 255, 255, 255;
9 | }
10 |
11 | @media (prefers-color-scheme: dark) {
12 | :root {
13 | --foreground-rgb: 255, 255, 255;
14 | --background-start-rgb: 0, 0, 0;
15 | --background-end-rgb: 0, 0, 0;
16 | }
17 | }
18 |
19 | body {
20 | color: rgb(var(--foreground-rgb));
21 | background: linear-gradient(
22 | to bottom,
23 | transparent,
24 | rgb(var(--background-end-rgb))
25 | )
26 | rgb(var(--background-start-rgb));
27 | }
28 |
29 | @layer utilities {
30 | .text-balance {
31 | text-wrap: balance;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "nextjsauth",
3 | "version": "0.1.0",
4 | "private": true,
5 | "scripts": {
6 | "dev": "next dev",
7 | "build": "next build",
8 | "start": "next start",
9 | "lint": "next lint"
10 | },
11 | "dependencies": {
12 | "jsonwebtoken": "^9.0.2",
13 | "mongoose": "^8.2.2",
14 | "next": "^14.1.4",
15 | "nodemailer": "^6.9.13",
16 | "react": "^18",
17 | "react-dom": "^18"
18 | },
19 | "devDependencies": {
20 | "@types/bcryptjs": "^2.4.6",
21 | "@types/jsonwebtoken": "^9.0.6",
22 | "@types/node": "^20",
23 | "@types/nodemailer": "^6.4.14",
24 | "@types/react": "^18",
25 | "@types/react-dom": "^18",
26 | "autoprefixer": "^10.0.1",
27 | "eslint": "^8",
28 | "eslint-config-next": "14.1.4",
29 | "postcss": "^8",
30 | "tailwindcss": "^3.3.0",
31 | "typescript": "^5"
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/models/userModel.js:
--------------------------------------------------------------------------------
1 | import mongoose from 'mongoose';
2 |
3 | const userSchema = new mongoose.Schema({
4 | username: {
5 | type: String,
6 | required: [true, 'Please provide an username'],
7 | unique: true,
8 | },
9 | email: {
10 | type: String,
11 | required: [true, 'Please provide an email'],
12 | unique: true,
13 | },
14 | password: {
15 | type: String,
16 | required: [true, 'Please provide a password'],
17 | },
18 | isVerified: {
19 | type: Boolean,
20 | default: false,
21 | },
22 | isAdmin: {
23 | type: Boolean,
24 | default: false,
25 | },
26 | forgotPasswordToken: {
27 | type: String,
28 | },
29 | forgotPasswordTokenExpire: {
30 | type: Date,
31 | },
32 | verifyToken: {
33 | type: String,
34 | },
35 | verifyTokenExpire: {
36 | type: Date,
37 | },
38 | })
39 |
40 | const User = mongoose.model('User', userSchema);
41 |
42 | export default User;
--------------------------------------------------------------------------------
/src/app/api/users/verifyemail/route.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "@/dbConfig/dbConfig";
2 | import User from "@/models/userModel";
3 | import { NextRequest, NextResponse } from "next/server";
4 |
5 | connect();
6 |
7 | export async function POST(request: NextRequest) {
8 | try {
9 | const reqBody = await request.json();
10 | const { token } = reqBody;
11 | console.log(token);
12 |
13 | const user = await User.findOne({
14 | verifyToken: token,
15 | verifyTokenExpire: { $gt: Date.now() },
16 | });
17 |
18 | if (!user) {
19 | return NextResponse.json({ error: "Invalid Token" }, { status: 400 });
20 | }
21 | console.log(user);
22 |
23 | user.isVerified = true;
24 | user.verifyToken = undefined;
25 | user.verifyTokenExpire = undefined;
26 |
27 | await user.save();
28 |
29 | return NextResponse.json(
30 | {
31 | message: "Email Verified Successfully",
32 | success: true,
33 | },
34 | { status: 500 }
35 | );
36 | } catch (error: any) {
37 | return NextResponse.json({ error: error.message }, { status: 500 });
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/public/next.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/app/api/users/signup/route.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "@/dbConfig/dbConfig";
2 | import User from "@/models/userModel";
3 | import { NextRequest, NextResponse } from "next/server";
4 | import bcryptjs from "bcryptjs";
5 | import { sendEmail } from "@/helpers/mailer";
6 |
7 | connect();
8 |
9 | export async function POST(request: NextRequest) {
10 | try {
11 | const reqBody = await request.json();
12 | const { username, email, password } = reqBody;
13 | console.log(reqBody);
14 |
15 | const user = await User.findOne({ email });
16 |
17 | if (user) {
18 | return NextResponse.json({ error: "User already exists" },{ status: 400 })
19 | }
20 |
21 | const salt = await bcryptjs.genSalt(10);
22 | const hashedPassword = await bcryptjs.hash(password, salt);
23 |
24 | const newUser = new User({
25 | username,
26 | email,
27 | password: hashedPassword,
28 | });
29 |
30 | const savedUser = await newUser.save();
31 | console.log(savedUser);
32 |
33 | //send verification email
34 | await sendEmail({email, emailType: "VERIFY", userId: savedUser._id});
35 |
36 | return NextResponse.json({
37 | message: "User registered successfully",
38 | success: true,
39 | savedUser
40 | })
41 |
42 | } catch (error: any) {
43 | return NextResponse.json({ error: error.message }, { status: 500 });
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/app/api/users/login/route.ts:
--------------------------------------------------------------------------------
1 | import { connect } from "@/dbConfig/dbConfig";
2 | import User from "@/models/userModel";
3 | import { NextRequest, NextResponse } from "next/server";
4 | import bcryptjs from "bcryptjs";
5 | import jwt from "jsonwebtoken";
6 |
7 | connect();
8 |
9 | export async function POST(request: NextRequest) {
10 | try {
11 | const reqBody = await request.json();
12 | const { email, password } = reqBody;
13 | console.log(reqBody);
14 |
15 | const user = await User.findOne({ email });
16 |
17 | if (!user) {
18 | return NextResponse.json(
19 | { error: "User doesn't exist" },
20 | { status: 400 }
21 | );
22 | }
23 | console.log("User exist");
24 |
25 | const validPassword = await bcryptjs.compare(password, user.password);
26 |
27 | if (!validPassword) {
28 | return NextResponse.json(
29 | { error: "Check your credentials" },
30 | { status: 400 }
31 | );
32 | }
33 |
34 | const tokenData = {
35 | id: user._id,
36 | username: user.username,
37 | email: user.email,
38 | };
39 |
40 | const token = await jwt.sign(tokenData, process.env.TOKEN_SECRET!, {
41 | expiresIn: "1h",
42 | });
43 |
44 | const response = NextResponse.json({
45 | message: "Logged in Success",
46 | status: true
47 | })
48 |
49 | response.cookies.set("token", token, {
50 | httpOnly: true
51 | })
52 |
53 | return response
54 |
55 | } catch (error: any) {
56 | return NextResponse.json({ error: error.message }, { status: 500 });
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/helpers/mailer.ts:
--------------------------------------------------------------------------------
1 | import User from "@/models/userModel";
2 | import nodemailer from "nodemailer";
3 | import bcryptjs from "bcryptjs";
4 |
5 | export const sendEmail = async ({ email, emailType, userId }: any) => {
6 | try {
7 | const hashedToken = await bcryptjs.hash(userId.toString(), 10);
8 |
9 | if (emailType === "VERIFY") {
10 | await User.findByIdAndUpdate(userId, {
11 | verifyToken: hashedToken,
12 | verifyTokenExpiry: Date.now() + 3600000,
13 | });
14 | } else if (emailType === "RESET") {
15 | await User.findByIdAndUpdate(userId, {
16 | forgotPasswordToken: hashedToken,
17 | forgotPasswordTokenExpiry: Date.now() + 3600000,
18 | });
19 | }
20 |
21 | var transport = nodemailer.createTransport({
22 | host: "sandbox.smtp.mailtrap.io",
23 | port: 2525,
24 | auth: {
25 | user: process.env.USER,
26 | pass: process.env.PASS,
27 | },
28 | });
29 |
30 | const mailOptions = {
31 | from: "vishalparmar3108@gmail.com",
32 | to: email,
33 | subject:
34 | emailType === "VERIFY" ? "Verify your email" : "Reset your password",
35 | html: `Click here to ${
38 | emailType === "VERIFY" ? "verify your email" : "reset your password"
39 | }
40 | or copy and paste the link below in your browser
41 | ${process.env.DOMAIN}/verifyemail?token=${hashedToken}
42 |
`,
43 | };
44 |
45 | const mailResponse = await transport.sendMail(mailOptions);
46 | return mailResponse;
47 | } catch (error: any) {
48 | throw new Error(error.message);
49 | }
50 | };
51 |
--------------------------------------------------------------------------------
/src/app/page.tsx:
--------------------------------------------------------------------------------
1 | import Image from "next/image";
2 |
3 | export default function Home() {
4 | return (
5 |
6 |
7 |
8 | Get started by editing
9 | src/app/page.tsx
10 |
11 |
29 |
30 |
31 |
32 |
40 |
41 |
42 |
111 |
112 | );
113 | }
114 |
--------------------------------------------------------------------------------