A robust full-stack authentication system featuring Next.js, Express, and Vite. Integrated with NextAuth.js and Passport.js for social logins, Prisma with Supabase (PostgreSQL) for data management, and Stripe for payments. Designed with modern UI/UX using Tailwind CSS and GSAP animations.
✅ Google & GitHub OAuth authentication
✅ Protected routes with middleware
✅ User profiles with avatar uploads
✅ Supabase PostgreSQL database
✅ Supabase Storage for file uploads
✅ Type-safe with TypeScript
✅ Modern UI with TailwindCSS
- Providers: Users authenticate using Google or GitHub OAuth providers.
- Session Strategy: Uses JWT (JSON Web Tokens) for fast, stateless session management, combined with database synchronization when required.
- Callbacks:
jwt: Encodes necessary user information (e.g., ID, active status) into the token.session: Attaches information from the JWT to the session object accessible on the client side.
- Middleware: Intercepts requests to protected routes (
/dashboard). If the user is unauthenticated, they are redirected to/login.
The system uses Supabase (PostgreSQL) as the primary database.
User: Stores core user details (e.g.,name,email,imagefor avatars).Account: Links theUserto their respective OAuth providers (Google, GitHub) securing access tokens and provider IDs.Session: Used if database sessions are enabled instead of strictly JWT-based sessions.VerificationToken: Stores tokens for potentially passwordless sign-in or email verification flows.
- Bucket (
avatars): A public bucket used specifically to host user-uploaded profile pictures. - Upload Process:
- Users select an image using the
avatar-uploadcomponent. - The application uploads the file securely to the
avatarsbucket. - The resulting public URL is saved to the
imagefield in theUsertable and immediately displayed in the UI.
- Users select an image using the
All auth endpoints (/login, /register, /reset) are protected by a two-layer system stored in the RateLimit Prisma table.
| Setting | Value |
|---|---|
| Window | 15 minutes |
| Max requests | 20 per window |
| On breach | Blocked until window expires |
Applied to every public auth route regardless of credentials. Defends against distributed credential-stuffing.
| Setting | Value |
|---|---|
| Window | 15 minutes |
| Max failures | 5 per window |
| Lock duration | 30 minutes |
Applied to /login only (after IP passes). On 5 consecutive failures the RateLimit.lockedUntil field is set, blocking that email for 30 minutes even from different IPs.
All auth responses use deliberately vague messages to prevent user-enumeration attacks:
| Scenario | Message shown to user |
|---|---|
| Email not registered | "Invalid credentials." |
| Wrong password | "Invalid credentials." |
| Email not verified | "Invalid credentials." |
| Account blocked | "Invalid credentials." |
| Account temp-locked | "Too many failed attempts. Try again in N minutes." |
| Password reset (any email) | "If that email is registered, you will receive a reset link shortly." |
| Registration (duplicate email) | "If this email is not yet registered, you will receive a verification link shortly." |
Why? An attacker who can distinguish "email not found" from "wrong password" can harvest valid emails silently. Generic messages eliminate this surface.
npm installCopy .env.example to .env and fill in your credentials:
cp .env.example .envYou'll need:
- Supabase Project: Create at supabase.com
- Google OAuth: Create at Google Cloud Console
- GitHub OAuth: Create at GitHub Developer Settings
- Go to Google Cloud Console
- Create a new project or select existing
- Enable Google+ API
- Create OAuth 2.0 credentials
- Add authorized redirect URI:
http://localhost:3000/api/auth/callback/google - Copy Client ID and Client Secret to
.env
- Go to GitHub Developer Settings
- Click "New OAuth App"
- Set Authorization callback URL:
http://localhost:3000/api/auth/callback/github - Copy Client ID and Client Secret to
.env
- Create a new project at supabase.com
- Copy the database connection string (Settings → Database → Connection String → URI)
- Update
DATABASE_URLin.env - Create a storage bucket named
avatars:- Go to Storage in Supabase dashboard
- Create a new bucket:
avatars - Set it to public
- Copy Project URL and Anon Key to
.env
npx prisma generate
npx prisma db pushopenssl rand -base64 32Add the output to NEXTAUTH_SECRET in .env
npm run devauth/
├── app/
│ ├── api/
│ │ ├── auth/[...nextauth]/route.ts # Auth.js handlers
│ │ └── upload-avatar/route.ts # Avatar upload API
│ ├── dashboard/page.tsx # Protected dashboard
│ ├── login/page.tsx # Login page
│ └── page.tsx # Landing page
├── components/
│ ├── avatar-upload.tsx # Avatar upload component
│ └── logout-button.tsx # Logout button
├── lib/
│ └── supabase.ts # Supabase client
├── prisma/
│ └── schema.prisma # Database schema
├── types/
│ └── next-auth.d.ts # Auth.js type extensions
├── auth.ts # Auth.js configuration
└── middleware.ts # Route protection
Deploy to Vercel:
- Push to GitHub
- Import to Vercel
- Add all environment variables
- Update
NEXTAUTH_URLto production URL - Update OAuth redirect URIs to production URL
MIT