├── .eslintrc.json ├── .gitignore ├── README.md ├── app ├── (auth) │ ├── change-password │ │ └── page.tsx │ ├── layout.tsx │ ├── profile │ │ └── page.tsx │ ├── signin │ │ └── page.tsx │ └── signup │ │ └── page.tsx ├── (root) │ └── page.tsx ├── api │ └── auth │ │ └── [...nextauth] │ │ └── route.ts ├── dashboard │ └── page.tsx ├── error │ └── page.tsx ├── favicon.ico ├── globals.css ├── layout.tsx └── unauthorized │ └── page.tsx ├── components.json ├── components ├── button │ ├── google-signin-button.tsx │ └── signout-button.tsx ├── form │ ├── change-password-form.tsx │ ├── signin-form.tsx │ ├── signup-form.tsx │ └── update-form.tsx ├── shared │ ├── footer.tsx │ ├── main-nav.tsx │ ├── mode-toggle.tsx │ ├── navbar.tsx │ ├── user-avatar.tsx │ └── user-nav.tsx └── ui │ ├── avatar.tsx │ ├── button.tsx │ ├── dropdown-menu.tsx │ ├── form.tsx │ ├── input.tsx │ ├── label.tsx │ ├── toast.tsx │ ├── toaster.tsx │ └── use-toast.ts ├── constants └── index.ts ├── lib ├── actions │ └── auth.actions.ts ├── models │ └── user.model.ts ├── mongodb.ts ├── nextauth-options.ts ├── utils.ts ├── utils │ └── token.ts └── validations │ └── auth.ts ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── providers ├── auth-provider.tsx └── theme-provider.tsx ├── public ├── next.svg └── vercel.svg ├── tailwind.config.js ├── tailwind.config.ts ├── tsconfig.json └── types └── next-auth.d.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals", 3 | "rules": { 4 | "indent": ["warn", 2] 5 | } 6 | } -------------------------------------------------------------------------------- /.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 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Next.js 13, NextAuth.js, MongoDB, Typescript Example 2 | A demo project that uses NextAuth.js for authentication, connects to MongoDB with Mongoose, and supports Google OAuth and email/password login. 3 | 4 | ## 🚀🌍 New Version Available (Next.js 14 + NextAuth.js v5 + i18n) 5 | Check out the new version of this project [here](https://github.com/wei30172/nextauth-v5-mongodb-typescript-example). 6 | 7 | ## Features 8 | - OAuth: Log in with Google. 9 | 10 | - Credential Login: Log in with email and password. 11 | 12 | - Profile Edit: Change user details. 13 | 14 | - Password Change: Safely update passwords. 15 | 16 | - Secure Routes: Access only for logged-in users / admins. 17 | 18 | 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). 19 | 20 | ## Environment Setup 21 | Create a .env file in the root directory and add the following variables: 22 | 23 | ```env 24 | NEXTAUTH_URL="http://localhost:3000" 25 | NEXTAUTH_SECRET="YOUR_NEXTAUTH_SECRET" 26 | 27 | GOOGLE_CLIENT_ID="YOUR_GOOGLE_CLIENT_ID" 28 | GOOGLE_CLIENT_SECRET="YOUR_GOOGLE_CLIENT_SECRET" 29 | 30 | MONGODB_URI="YOUR_MONGODB_URI" 31 | ``` 32 | 33 | GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET 34 | 35 | - Navigate to [https://console.cloud.google.com](https://console.cloud.google.com/) . 36 | 37 | - Create a new project. 38 | 39 | - Head over to APIs & Services => Credentials. 40 | 41 | - Click on CREATE CREDENTIALS => OAuth client ID. 42 | 43 | - Choose the Web application. 44 | 45 | - Add to Authorized JavaScript origins: http://localhost:3000 . 46 | 47 | - Add to Authorized redirect URIs: http://localhost:3000/api/auth/callback/google. 48 | 49 | - Finish by going to APIs & Services => OAuth consent screen and publishing the app. 50 | 51 | ## Getting Started 52 | 53 | First, run the development server: 54 | 55 | ```bash 56 | npm run dev 57 | # or 58 | yarn dev 59 | # or 60 | pnpm dev 61 | ``` 62 | 63 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 64 | 65 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 66 | 67 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 68 | 69 | ## Learn More 70 | 71 | To learn more about Next.js, take a look at the following resources: 72 | 73 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 74 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 75 | 76 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 77 | 78 | ## Deploy on Vercel 79 | 80 | 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. 81 | 82 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 83 | -------------------------------------------------------------------------------- /app/(auth)/change-password/page.tsx: -------------------------------------------------------------------------------- 1 | import ChangePasswordForm from "@/components/form/change-password-form" 2 | import { changeUserPassword } from "@/lib/actions/auth.actions" 3 | 4 | const ChangePasswordPage = async () => { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | 13 | export default ChangePasswordPage -------------------------------------------------------------------------------- /app/(auth)/layout.tsx: -------------------------------------------------------------------------------- 1 | interface AuthLayoutProps { 2 | children: React.ReactNode 3 | } 4 | 5 | export default function AuthLayout({ 6 | children 7 | }: AuthLayoutProps) { 8 | return ( 9 |
10 | {children} 11 |
12 | ) 13 | } -------------------------------------------------------------------------------- /app/(auth)/profile/page.tsx: -------------------------------------------------------------------------------- 1 | import UpdateForm from "@/components/form/update-form" 2 | import { updateUserProfile } from "@/lib/actions/auth.actions" 3 | 4 | const ProfilePage = async () => { 5 | return ( 6 |
7 | 8 |
9 | ) 10 | } 11 | 12 | 13 | export default ProfilePage 14 | -------------------------------------------------------------------------------- /app/(auth)/signin/page.tsx: -------------------------------------------------------------------------------- 1 | import SignInForm from '@/components/form/signin-form' 2 | 3 | interface SignInPageProps { 4 | searchParams: { 5 | callbackUrl: string 6 | } 7 | } 8 | const SignInPage = ({ 9 | searchParams: { callbackUrl } 10 | }: SignInPageProps) => { 11 | // console.log(callbackUrl) 12 | return ( 13 |
14 | 15 |
16 | ) 17 | } 18 | 19 | export default SignInPage -------------------------------------------------------------------------------- /app/(auth)/signup/page.tsx: -------------------------------------------------------------------------------- 1 | import { signUpWithCredentials } from "@/lib/actions/auth.actions" 2 | import SignUpForm from "@/components/form/signup-form" 3 | 4 | interface SignUpPageProps { 5 | searchParams: { 6 | callbackUrl: string 7 | } 8 | } 9 | 10 | const SignUpPage = ({ 11 | searchParams: { callbackUrl } 12 | }: SignUpPageProps) => { 13 | return ( 14 |
15 | 19 |
20 | ) 21 | } 22 | 23 | export default SignUpPage -------------------------------------------------------------------------------- /app/(root)/page.tsx: -------------------------------------------------------------------------------- 1 | export default function Home() { 2 | return ( 3 |
4 |

Welcome to the Home Page

5 |
6 | ) 7 | } 8 | -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import NextAuth from "next-auth" 2 | import { nextauthOptions } from "@/lib/nextauth-options" 3 | 4 | const handler = NextAuth(nextauthOptions) 5 | 6 | export { handler as GET, handler as POST } -------------------------------------------------------------------------------- /app/dashboard/page.tsx: -------------------------------------------------------------------------------- 1 | const Dashboard = () => { 2 | return ( 3 |

Hi, admin. Welcome to the Dashboard

4 | ) 5 | } 6 | 7 | export default Dashboard -------------------------------------------------------------------------------- /app/error/page.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useRouter, useSearchParams } from "next/navigation" 4 | import { Button } from "@/components/ui/button" 5 | 6 | const Error = () => { 7 | const router = useRouter() 8 | const searchParams = useSearchParams() 9 | const errMsg = searchParams.get("error") 10 | 11 | return ( 12 |
13 |

Errors: {errMsg}

14 | 21 |
22 | ) 23 | } 24 | 25 | export default Error -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wei30172/nextauth-mongodb-typescript-example/813a1884670d5fbd914161f811c0c2855d0abaea/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | html, 6 | body, 7 | :root { 8 | height: 100%; 9 | } 10 | 11 | @layer base { 12 | :root { 13 | --background: 0 0% 100%; 14 | --foreground: 222.2 84% 4.9%; 15 | 16 | --card: 0 0% 100%; 17 | --card-foreground: 222.2 84% 4.9%; 18 | 19 | --popover: 0 0% 100%; 20 | --popover-foreground: 222.2 84% 4.9%; 21 | 22 | --primary: 222.2 47.4% 11.2%; 23 | --primary-foreground: 210 40% 98%; 24 | 25 | --secondary: 210 40% 96.1%; 26 | --secondary-foreground: 222.2 47.4% 11.2%; 27 | 28 | --muted: 210 40% 96.1%; 29 | --muted-foreground: 215.4 16.3% 46.9%; 30 | 31 | --accent: 210 40% 96.1%; 32 | --accent-foreground: 222.2 47.4% 11.2%; 33 | 34 | --destructive: 0 84.2% 60.2%; 35 | --destructive-foreground: 210 40% 98%; 36 | 37 | --border: 214.3 31.8% 91.4%; 38 | --input: 214.3 31.8% 91.4%; 39 | --ring: 222.2 84% 4.9%; 40 | 41 | --radius: 0.5rem; 42 | } 43 | 44 | .dark { 45 | --background: 222.2 84% 4.9%; 46 | --foreground: 210 40% 98%; 47 | 48 | --card: 222.2 84% 4.9%; 49 | --card-foreground: 210 40% 98%; 50 | 51 | --popover: 222.2 84% 4.9%; 52 | --popover-foreground: 210 40% 98%; 53 | 54 | --primary: 210 40% 98%; 55 | --primary-foreground: 222.2 47.4% 11.2%; 56 | 57 | --secondary: 217.2 32.6% 17.5%; 58 | --secondary-foreground: 210 40% 98%; 59 | 60 | --muted: 217.2 32.6% 17.5%; 61 | --muted-foreground: 215 20.2% 65.1%; 62 | 63 | --accent: 217.2 32.6% 17.5%; 64 | --accent-foreground: 210 40% 98%; 65 | 66 | --destructive: 0 62.8% 30.6%; 67 | --destructive-foreground: 210 40% 98%; 68 | 69 | --border: 217.2 32.6% 17.5%; 70 | --input: 217.2 32.6% 17.5%; 71 | --ring: 212.7 26.8% 83.9%; 72 | } 73 | } 74 | 75 | @layer base { 76 | * { 77 | @apply border-border; 78 | } 79 | body { 80 | @apply bg-background text-foreground; 81 | } 82 | } -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import './globals.css' 2 | import type { Metadata } from 'next' 3 | import { Inter } from 'next/font/google' 4 | 5 | import AuthProvider from '@/providers/auth-provider' 6 | import ThemeProvider from '@/providers/theme-provider' 7 | import Navbar from '@/components/shared/navbar' 8 | import Footer from '@/components/shared/footer' 9 | import { Toaster } from '@/components/ui/toaster' 10 | 11 | const inter = Inter({ subsets: ['latin'] }) 12 | 13 | export const metadata: Metadata = { 14 | title: 'Nextjs fullstack Authentication', 15 | description: 'Sign-Up and Sign-In with Nextjs', 16 | } 17 | 18 | export default function RootLayout({ 19 | children, 20 | }: { 21 | children: React.ReactNode 22 | }) { 23 | return ( 24 | 25 | 26 | 27 | 33 | 34 |
35 | {children} 36 |
37 |