├── .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 |
12 | 18 | By{" "} 19 | Vercel Logo 27 | 28 |
29 |
30 | 31 |
32 | Next.js Logo 40 |
41 | 42 |
43 | 49 |

50 | Docs{" "} 51 | 52 | -> 53 | 54 |

55 |

56 | Find in-depth information about Next.js features and API. 57 |

58 |
59 | 60 | 66 |

67 | Learn{" "} 68 | 69 | -> 70 | 71 |

72 |

73 | Learn about Next.js in an interactive course with quizzes! 74 |

75 |
76 | 77 | 83 |

84 | Templates{" "} 85 | 86 | -> 87 | 88 |

89 |

90 | Explore starter templates for Next.js. 91 |

92 |
93 | 94 | 100 |

101 | Deploy{" "} 102 | 103 | -> 104 | 105 |

106 |

107 | Instantly deploy your Next.js site to a shareable URL with Vercel. 108 |

109 |
110 |
111 |
112 | ); 113 | } 114 | --------------------------------------------------------------------------------