├── .eslintrc.json ├── .env.template ├── src ├── app │ ├── favicon.ico │ ├── about │ │ └── page.tsx │ ├── page.tsx │ ├── Providers.tsx │ ├── layout.tsx │ ├── globals.css │ ├── dashboard │ │ └── profile │ │ │ └── page.tsx │ ├── api │ │ └── auth │ │ │ ├── [...nextauth] │ │ │ └── route.ts │ │ │ └── signup │ │ │ └── route.ts │ ├── login │ │ └── page.tsx │ └── register │ │ └── page.tsx ├── middleware.ts ├── libs │ └── mongodb.ts ├── models │ └── user.ts └── components │ └── Navbar.jsx ├── next.config.js ├── postcss.config.js ├── .gitignore ├── tailwind.config.js ├── public ├── vercel.svg └── next.svg ├── tsconfig.json ├── package.json └── README.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.env.template: -------------------------------------------------------------------------------- 1 | MONGODB_URI=mongodb://localhost/nextmongo 2 | NEXTAUTH_SECRET=secret -------------------------------------------------------------------------------- /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fazt/next-auth-credentials-mongodb/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = {} 3 | 4 | module.exports = nextConfig 5 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /src/app/about/page.tsx: -------------------------------------------------------------------------------- 1 | function AboutPage() { 2 | return ( 3 |
AboutPage
4 | ) 5 | } 6 | 7 | export default AboutPage -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | export { default } from "next-auth/middleware"; 2 | 3 | export const config = { matcher: ["/", "/dashboard/:path*"] }; 4 | -------------------------------------------------------------------------------- /src/app/page.tsx: -------------------------------------------------------------------------------- 1 | function HomePage() { 2 | // const { data: session, status } = useSession({ 3 | // required: true, 4 | // }); 5 | // console.log(session, status); 6 | 7 | return
HomePage
; 8 | } 9 | 10 | export default HomePage; 11 | -------------------------------------------------------------------------------- /src/app/Providers.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { SessionProvider } from "next-auth/react"; 4 | 5 | interface Props { 6 | children: React.ReactNode; 7 | } 8 | 9 | export default function Providers({ children }: Props) { 10 | return {children}; 11 | } 12 | -------------------------------------------------------------------------------- /.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 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | './src/pages/**/*.{js,ts,jsx,tsx,mdx}', 5 | './src/components/**/*.{js,ts,jsx,tsx,mdx}', 6 | './src/app/**/*.{js,ts,jsx,tsx,mdx}', 7 | ], 8 | theme: { 9 | extend: { 10 | backgroundImage: { 11 | 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', 12 | 'gradient-conic': 13 | 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', 14 | }, 15 | }, 16 | }, 17 | plugins: [], 18 | } 19 | -------------------------------------------------------------------------------- /src/libs/mongodb.ts: -------------------------------------------------------------------------------- 1 | import mongoose from "mongoose"; 2 | 3 | const { MONGODB_URI } = process.env; 4 | 5 | if (!MONGODB_URI) { 6 | throw new Error("MONGODB_URI must be defined"); 7 | } 8 | 9 | export const connectDB = async () => { 10 | try { 11 | const { connection } = await mongoose.connect(MONGODB_URI); 12 | if (connection.readyState === 1) { 13 | console.log("MongoDB Connected"); 14 | return Promise.resolve(true); 15 | } 16 | } catch (error) { 17 | console.error(error); 18 | return Promise.reject(error); 19 | } 20 | }; 21 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import Providers from "./Providers"; 2 | import Navbar from "../components/Navbar"; 3 | import "./globals.css"; 4 | 5 | export const metadata = { 6 | title: "Create Next App", 7 | description: "Generated by create next app", 8 | }; 9 | 10 | export default function RootLayout({ 11 | children, 12 | }: { 13 | children: React.ReactNode; 14 | }) { 15 | return ( 16 | 17 | 18 | 19 | 20 |
{children}
21 |
22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true, 17 | "plugins": [ 18 | { 19 | "name": "next" 20 | } 21 | ], 22 | "paths": { 23 | "@/*": ["./src/*"] 24 | } 25 | }, 26 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "next-auth-mongodb", 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 | "@types/node": "20.3.0", 13 | "@types/react": "18.2.11", 14 | "@types/react-dom": "18.2.4", 15 | "autoprefixer": "10.4.14", 16 | "bcryptjs": "^2.4.3", 17 | "eslint": "8.42.0", 18 | "eslint-config-next": "13.4.5", 19 | "mongoose": "^7.2.3", 20 | "next": "13.4.5", 21 | "next-auth": "^4.22.1", 22 | "postcss": "8.4.24", 23 | "react": "18.2.0", 24 | "react-dom": "18.2.0", 25 | "tailwindcss": "3.3.2", 26 | "typescript": "5.1.3" 27 | }, 28 | "devDependencies": { 29 | "@types/bcryptjs": "^2.4.2" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/models/user.ts: -------------------------------------------------------------------------------- 1 | import { Schema, model, models } from "mongoose"; 2 | 3 | const UserSchema = new Schema( 4 | { 5 | email: { 6 | type: String, 7 | unique: true, 8 | required: [true, "Email is required"], 9 | match: [ 10 | /^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/, 11 | "Email is invalid", 12 | ], 13 | }, 14 | password: { 15 | type: String, 16 | required: [true, "Password is required"], 17 | select: false, 18 | }, 19 | fullname: { 20 | type: String, 21 | required: [true, "fullname is required"], 22 | minLength: [3, "fullname must be at least 3 characters"], 23 | maxLength: [20, "fullname must be at most 20 characters"], 24 | }, 25 | }, 26 | { 27 | timestamps: true, 28 | } 29 | ); 30 | 31 | const User = models.User || model("User", UserSchema); 32 | export default User; 33 | -------------------------------------------------------------------------------- /src/app/dashboard/profile/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { useSession, signOut } from "next-auth/react"; 3 | 4 | function ProfilePage() { 5 | const { data: session, status } = useSession(); 6 | 7 | console.log(session, status); 8 | 9 | return ( 10 |
11 |

Profile

12 | 13 |
14 |         {JSON.stringify(
15 |           {
16 |             session,
17 |             status,
18 |           },
19 |           null,
20 |           2
21 |         )}
22 |       
23 | 24 | 32 |
33 | ); 34 | } 35 | 36 | export default ProfilePage; 37 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { getServerSession } from "next-auth"; 3 | 4 | async function Navbar() { 5 | const session = await getServerSession(); 6 | 7 | return ( 8 | 37 | ); 38 | } 39 | 40 | export default Navbar; 41 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /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 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 20 | 21 | ## Learn More 22 | 23 | To learn more about Next.js, take a look at the following resources: 24 | 25 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 26 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 27 | 28 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 29 | 30 | ## Deploy on Vercel 31 | 32 | 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. 33 | 34 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 35 | -------------------------------------------------------------------------------- /src/app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { connectDB } from "@/libs/mongodb"; 2 | import User from "@/models/user"; 3 | import NextAuth from "next-auth"; 4 | import CredentialsProvider from "next-auth/providers/credentials"; 5 | import bcrypt from "bcryptjs"; 6 | 7 | const handler = NextAuth({ 8 | providers: [ 9 | CredentialsProvider({ 10 | name: "Credentials", 11 | id: "credentials", 12 | credentials: { 13 | email: { label: "Email", type: "text", placeholder: "jsmith" }, 14 | password: { label: "Password", type: "password" }, 15 | }, 16 | async authorize(credentials) { 17 | await connectDB(); 18 | const userFound = await User.findOne({ 19 | email: credentials?.email, 20 | }).select("+password"); 21 | 22 | if (!userFound) throw new Error("Invalid credentials"); 23 | 24 | const passwordMatch = await bcrypt.compare( 25 | credentials!.password, 26 | userFound.password 27 | ); 28 | 29 | if (!passwordMatch) throw new Error("Invalid credentials"); 30 | 31 | console.log(userFound); 32 | 33 | return userFound; 34 | }, 35 | }), 36 | ], 37 | pages: { 38 | signIn: "/login", 39 | }, 40 | session: { 41 | strategy: "jwt", 42 | }, 43 | callbacks: { 44 | async jwt({ token, user }) { 45 | if (user) token.user = user; 46 | return token; 47 | }, 48 | async session({ session, token }) { 49 | session.user = token.user as any; 50 | return session; 51 | }, 52 | }, 53 | }); 54 | 55 | export { handler as GET, handler as POST }; 56 | -------------------------------------------------------------------------------- /src/app/api/auth/signup/route.ts: -------------------------------------------------------------------------------- 1 | import { connectDB } from "@/libs/mongodb"; 2 | import User from "@/models/user"; 3 | import { NextResponse } from "next/server"; 4 | import bcrypt from "bcryptjs"; 5 | import mongoose from "mongoose"; 6 | 7 | export async function POST(request: Request) { 8 | try { 9 | await connectDB(); 10 | 11 | const { fullname, email, password } = await request.json(); 12 | 13 | if (password < 6) 14 | return NextResponse.json( 15 | { message: "Password must be at least 6 characters" }, 16 | { status: 400 } 17 | ); 18 | 19 | const userFound = await User.findOne({ email }); 20 | 21 | if (userFound) 22 | return NextResponse.json( 23 | { 24 | message: "Email already exists", 25 | }, 26 | { 27 | status: 409, 28 | } 29 | ); 30 | 31 | const hashedPassword = await bcrypt.hash(password, 12); 32 | 33 | const user = new User({ 34 | fullname, 35 | email, 36 | password: hashedPassword, 37 | }); 38 | 39 | const savedUser = await user.save(); 40 | console.log(savedUser); 41 | 42 | return NextResponse.json( 43 | { 44 | fullname, 45 | email, 46 | createdAt: savedUser.createdAt, 47 | updatedAt: savedUser.updatedAt, 48 | }, 49 | { status: 201 } 50 | ); 51 | } catch (error) { 52 | if (error instanceof mongoose.Error.ValidationError) { 53 | return NextResponse.json( 54 | { 55 | message: error.message, 56 | }, 57 | { 58 | status: 400, 59 | } 60 | ); 61 | } 62 | return NextResponse.error(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/app/login/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FormEvent, useState } from "react"; 3 | import { AxiosError } from "axios"; 4 | import { signIn } from "next-auth/react"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | function Signin() { 8 | const [error, setError] = useState(""); 9 | const router = useRouter(); 10 | 11 | const handleSubmit = async (event: FormEvent) => { 12 | event.preventDefault(); 13 | const formData = new FormData(event.currentTarget); 14 | const res = await signIn("credentials", { 15 | email: formData.get("email"), 16 | password: formData.get("password"), 17 | redirect: false, 18 | }); 19 | 20 | if (res?.error) setError(res.error as string); 21 | 22 | if (res?.ok) return router.push("/dashboard/profile"); 23 | }; 24 | 25 | return ( 26 |
27 |
31 | {error &&
{error}
} 32 |

Signin

33 | 34 | 35 | 41 | 42 | 43 | 49 | 50 | 53 |
54 |
55 | ); 56 | } 57 | 58 | export default Signin; 59 | -------------------------------------------------------------------------------- /src/app/register/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | import { FormEvent, useState } from "react"; 3 | import axios, { AxiosError } from "axios"; 4 | import { signIn } from "next-auth/react"; 5 | import { useRouter } from "next/navigation"; 6 | 7 | function Signup() { 8 | const [error, setError] = useState(); 9 | const router = useRouter(); 10 | 11 | const handleSubmit = async (event: FormEvent) => { 12 | event.preventDefault(); 13 | try { 14 | const formData = new FormData(event.currentTarget); 15 | const signupResponse = await axios.post("/api/auth/signup", { 16 | email: formData.get("email"), 17 | password: formData.get("password"), 18 | fullname: formData.get("fullname"), 19 | }); 20 | console.log(signupResponse); 21 | const res = await signIn("credentials", { 22 | email: signupResponse.data.email, 23 | password: formData.get("password"), 24 | redirect: false, 25 | }); 26 | 27 | if (res?.ok) return router.push("/dashboard/profile"); 28 | } catch (error) { 29 | console.log(error); 30 | if (error instanceof AxiosError) { 31 | const errorMessage = error.response?.data.message; 32 | setError(errorMessage); 33 | } 34 | } 35 | }; 36 | 37 | return ( 38 |
39 |
40 | {error &&
{error}
} 41 |

Signup

42 | 43 | 44 | 50 | 51 | 52 | 58 | 59 | 60 | 66 | 67 | 70 |
71 |
72 | ); 73 | } 74 | 75 | export default Signup; 76 | --------------------------------------------------------------------------------