├── .eslintrc.json ├── .gitignore ├── LICENSE ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── src ├── app │ ├── api │ │ ├── auth │ │ │ └── [...nextauth] │ │ │ │ └── route.ts │ │ └── register │ │ │ └── route.ts │ ├── dashboard │ │ └── page.tsx │ ├── favicon.ico │ ├── globals.css │ ├── layout.tsx │ ├── login │ │ └── page.tsx │ ├── page.tsx │ └── register │ │ └── page.tsx ├── components │ └── Navbar.tsx ├── models │ └── User.js └── utils │ ├── SessionProvider.tsx │ └── db.js ├── tailwind.config.ts └── tsconfig.json /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.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 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | .env 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Umair 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | # or 14 | bun dev 15 | ``` 16 | 17 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 18 | 19 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-auth-register", 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 | "bcryptjs": "^2.4.3", 13 | "mongoose": "^7.5.3", 14 | "next": "latest", 15 | "next-auth": "^4.23.1", 16 | "react": "latest", 17 | "react-dom": "latest" 18 | }, 19 | "devDependencies": { 20 | "@types/node": "latest", 21 | "@types/react": "latest", 22 | "@types/react-dom": "latest", 23 | "autoprefixer": "latest", 24 | "eslint": "latest", 25 | "eslint-config-next": "latest", 26 | "postcss": "latest", 27 | "tailwindcss": "latest", 28 | "typescript": "latest" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth"; 2 | import { Account, User as AuthUser } from "next-auth"; 3 | import GithubProvider from "next-auth/providers/github"; 4 | import CredentialsProvider from "next-auth/providers/credentials"; 5 | import bcrypt from "bcryptjs"; 6 | import User from "@/models/User"; 7 | import connect from "@/utils/db"; 8 | 9 | export const authOptions: any = { 10 | // Configure one or more authentication providers 11 | providers: [ 12 | CredentialsProvider({ 13 | id: "credentials", 14 | name: "Credentials", 15 | credentials: { 16 | email: { label: "Email", type: "text" }, 17 | password: { label: "Password", type: "password" }, 18 | }, 19 | async authorize(credentials: any) { 20 | await connect(); 21 | try { 22 | const user = await User.findOne({ email: credentials.email }); 23 | if (user) { 24 | const isPasswordCorrect = await bcrypt.compare( 25 | credentials.password, 26 | user.password 27 | ); 28 | if (isPasswordCorrect) { 29 | return user; 30 | } 31 | } 32 | } catch (err: any) { 33 | throw new Error(err); 34 | } 35 | }, 36 | }), 37 | GithubProvider({ 38 | clientId: process.env.GITHUB_ID ?? "", 39 | clientSecret: process.env.GITHUB_SECRET ?? "", 40 | }), 41 | // ...add more providers here 42 | ], 43 | callbacks: { 44 | async signIn({ user, account }: { user: AuthUser; account: Account }) { 45 | if (account?.provider == "credentials") { 46 | return true; 47 | } 48 | if (account?.provider == "github") { 49 | await connect(); 50 | try { 51 | const existingUser = await User.findOne({ email: user.email }); 52 | if (!existingUser) { 53 | const newUser = new User({ 54 | email: user.email, 55 | }); 56 | 57 | await newUser.save(); 58 | return true; 59 | } 60 | return true; 61 | } catch (err) { 62 | console.log("Error saving user", err); 63 | return false; 64 | } 65 | } 66 | }, 67 | }, 68 | }; 69 | 70 | export const handler = NextAuth(authOptions); 71 | export { handler as GET, handler as POST }; 72 | -------------------------------------------------------------------------------- /src/app/api/register/route.ts: -------------------------------------------------------------------------------- 1 | import User from "@/models/User"; 2 | import connect from "@/utils/db"; 3 | import bcrypt from "bcryptjs"; 4 | import { NextResponse } from "next/server"; 5 | 6 | export const POST = async (request: any) => { 7 | const { email, password } = await request.json(); 8 | 9 | await connect(); 10 | 11 | const existingUser = await User.findOne({ email }); 12 | 13 | if (existingUser) { 14 | return new NextResponse("Email is already in use", { status: 400 }); 15 | } 16 | 17 | const hashedPassword = await bcrypt.hash(password, 5); 18 | const newUser = new User({ 19 | email, 20 | password: hashedPassword, 21 | }); 22 | 23 | try { 24 | await newUser.save(); 25 | return new NextResponse("user is registered", { status: 200 }); 26 | } catch (err: any) { 27 | return new NextResponse(err, { 28 | status: 500, 29 | }); 30 | } 31 | }; 32 | -------------------------------------------------------------------------------- /src/app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { getServerSession } from "next-auth"; 3 | import { redirect } from "next/navigation"; 4 | 5 | const Dashboard = async () => { 6 | const session = await getServerSession(); 7 | if (!session) { 8 | redirect("/"); 9 | } 10 | return ( 11 |
12 | Dashboard 13 |
14 | ); 15 | }; 16 | 17 | export default Dashboard; 18 | -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/umairjameel321/next-auth-mongodb/f2f9ae569a978113cd32be1811b6156d0b6bb14a/src/app/favicon.ico -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/Navbar"; 2 | import "./globals.css"; 3 | import type { Metadata } from "next"; 4 | import { Inter } from "next/font/google"; 5 | 6 | import { getServerSession } from "next-auth"; 7 | import SessionProvider from "@/utils/SessionProvider"; 8 | 9 | const inter = Inter({ subsets: ["latin"] }); 10 | 11 | export const metadata: Metadata = { 12 | title: "Create Next App", 13 | description: "Generated by create next app", 14 | }; 15 | 16 | export default async function RootLayout({ 17 | children, 18 | }: { 19 | children: React.ReactNode; 20 | }) { 21 | const session = await getServerSession(); 22 | return ( 23 | 24 | 25 | 26 |
27 | 28 | {children} 29 |
30 |
31 | 32 | 33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import Link from "next/link"; 4 | import { signIn, useSession } from "next-auth/react"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | const Login = () => { 8 | const router = useRouter(); 9 | const [error, setError] = useState(""); 10 | // const session = useSession(); 11 | const { data: session, status: sessionStatus } = useSession(); 12 | 13 | useEffect(() => { 14 | if (sessionStatus === "authenticated") { 15 | router.replace("/dashboard"); 16 | } 17 | }, [sessionStatus, router]); 18 | 19 | const isValidEmail = (email: string) => { 20 | const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; 21 | return emailRegex.test(email); 22 | }; 23 | 24 | const handleSubmit = async (e: any) => { 25 | e.preventDefault(); 26 | const email = e.target[0].value; 27 | const password = e.target[1].value; 28 | 29 | if (!isValidEmail(email)) { 30 | setError("Email is invalid"); 31 | return; 32 | } 33 | 34 | if (!password || password.length < 8) { 35 | setError("Password is invalid"); 36 | return; 37 | } 38 | 39 | const res = await signIn("credentials", { 40 | redirect: false, 41 | email, 42 | password, 43 | }); 44 | 45 | if (res?.error) { 46 | setError("Invalid email or password"); 47 | if (res?.url) router.replace("/dashboard"); 48 | } else { 49 | setError(""); 50 | } 51 | }; 52 | 53 | if (sessionStatus === "loading") { 54 | return

Loading...

; 55 | } 56 | 57 | return ( 58 | sessionStatus !== "authenticated" && ( 59 |
60 |
61 |

Login

62 |
63 | 69 | 75 | 82 |

{error && error}

83 |
84 | 92 |
- OR -
93 | 97 | Register Here 98 | 99 |
100 |
101 | ) 102 | ); 103 | }; 104 | 105 | export default Login; 106 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import Image from "next/image"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 |

Home Page

7 |
8 | ); 9 | } 10 | -------------------------------------------------------------------------------- /src/app/register/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React, { useEffect, useState } from "react"; 3 | import Link from "next/link"; 4 | import { useRouter } from "next/navigation"; 5 | import { useSession } from "next-auth/react"; 6 | 7 | const Register = () => { 8 | const [error, setError] = useState(""); 9 | const router = useRouter(); 10 | const { data: session, status: sessionStatus } = useSession(); 11 | 12 | useEffect(() => { 13 | if (sessionStatus === "authenticated") { 14 | router.replace("/dashboard"); 15 | } 16 | }, [sessionStatus, router]); 17 | 18 | const isValidEmail = (email: string) => { 19 | const emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i; 20 | return emailRegex.test(email); 21 | }; 22 | const handleSubmit = async (e: any) => { 23 | e.preventDefault(); 24 | const email = e.target[0].value; 25 | const password = e.target[1].value; 26 | 27 | if (!isValidEmail(email)) { 28 | setError("Email is invalid"); 29 | return; 30 | } 31 | 32 | if (!password || password.length < 8) { 33 | setError("Password is invalid"); 34 | return; 35 | } 36 | 37 | try { 38 | const res = await fetch("/api/register", { 39 | method: "POST", 40 | headers: { 41 | "Content-Type": "application/json", 42 | }, 43 | body: JSON.stringify({ 44 | email, 45 | password, 46 | }), 47 | }); 48 | if (res.status === 400) { 49 | setError("This email is already registered"); 50 | } 51 | if (res.status === 200) { 52 | setError(""); 53 | router.push("/login"); 54 | } 55 | } catch (error) { 56 | setError("Error, try again"); 57 | console.log(error); 58 | } 59 | }; 60 | 61 | if (sessionStatus === "loading") { 62 | return

Loading...

; 63 | } 64 | 65 | return ( 66 | sessionStatus !== "authenticated" && ( 67 |
68 |
69 |

Register

70 |
71 | 77 | 83 | 90 |

{error && error}

91 |
92 |
- OR -
93 | 97 | Login with an existing account 98 | 99 |
100 |
101 | ) 102 | ); 103 | }; 104 | 105 | export default Register; 106 | -------------------------------------------------------------------------------- /src/components/Navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import Link from "next/link"; 4 | import { signOut, useSession } from "next-auth/react"; 5 | 6 | const Navbar = () => { 7 | const { data: session }: any = useSession(); 8 | return ( 9 |
10 | 46 |
47 | ); 48 | }; 49 | 50 | export default Navbar; 51 | -------------------------------------------------------------------------------- /src/models/User.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const { Schema } = mongoose; 4 | 5 | const userSchema = new Schema( 6 | { 7 | email: { 8 | type: String, 9 | unique: true, 10 | required: true, 11 | }, 12 | password: { 13 | type: String, 14 | required: false, 15 | }, 16 | }, 17 | { timestamps: true } 18 | ); 19 | 20 | export default mongoose.models.User || mongoose.model("User", userSchema); 21 | -------------------------------------------------------------------------------- /src/utils/SessionProvider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import React from "react"; 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | const AuthProvider = ({ children }: any) => { 6 | return {children}; 7 | }; 8 | 9 | export default AuthProvider; 10 | -------------------------------------------------------------------------------- /src/utils/db.js: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const connect = async () => { 4 | if (mongoose.connections[0].readyState) return; 5 | 6 | try { 7 | await mongoose.connect(process.env.MONGO_URL, { 8 | useNewUrlParser: true, 9 | useUnifiedTopology: true, 10 | }); 11 | console.log("Mongo Connection successfully established."); 12 | } catch (error) { 13 | throw new Error("Error connecting to Mongoose"); 14 | } 15 | }; 16 | 17 | export default connect; 18 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | --------------------------------------------------------------------------------