AnonMessage uses a two-phase authentication system: new users verify their email address with a one-time password (OTP) before their account is created, and returning users authenticate through a NextAuthDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/dev0302/nextjs-project-1/llms.txt
Use this file to discover all available pages before exploring further.
CredentialsProvider backed by bcrypt password comparison. All sessions are stored as JWTs — no database session table is required.
Sign-Up Flow
Request an OTP
The client sends the user’s email to If the email is already taken the route returns
POST /api/send-otp. The server checks
that the email is not already registered, generates a unique 6-digit numeric
OTP via otp-generator, creates an OTP document in MongoDB, and triggers
an email via Brevo (through the mailSender helper). The OTP document has a
MongoDB TTL index set to 5 * 60 seconds, so it expires automatically after
five minutes.409 Conflict with
{ success: false, message: "User already registered" }.Submit credentials + OTP
The client sends A successful response is
{ username, email, password, otp } to POST /api/sign-up.
The server:- Validates that all four fields are present.
- Rejects with
409if the email already exists. - Fetches the most-recently-created OTP for that email and compares it with
the submitted value — a mismatch returns
401 Invalid OTP. - Hashes the password with
bcrypt.hash(password, 10). - Creates the user document with
isVerified: trueandisAcceptingMessages: true.
201 Created with the safe user object (no
hashed password).OTP documents carry a MongoDB TTL index of 5 minutes (
expires: 5 * 60
on the createdAt field). After five minutes the document is automatically
deleted, making the OTP permanently invalid even if the user never submits it.
The while (true) retry loop in send-otp ensures uniqueness — it retries
generation only on a duplicate-key error (code 11000).Sign-In Flow
Sign-in is handled by NextAuth’sCredentialsProvider. The authorize callback runs on every login attempt:
bcrypt.compare returns true, NextAuth receives the full Mongoose user document and proceeds to the jwt callback. Any thrown error message is surfaced back to the client through NextAuth’s error handling.
JWT Strategy and Custom Token Fields
AnonMessage usessession.strategy: "jwt" — no database is queried on every request to verify the session. The jwt callback enriches the token with four custom fields the first time it is created (when user is present):
session callback then copies those token fields onto session.user so they are accessible in both Server Components and client hooks:
Session Type Declarations
Because NextAuth’s built-in types do not include the custom fields,src/app/types/next-auth.d.ts uses TypeScript module augmentation to extend the User, Session, and JWT interfaces:
& DefaultSession['user'] intersection preserves the original name, email, and image fields so nothing is accidentally dropped.
Protected Routes (Middleware)
Route protection is implemented insrc/middleware.ts using getToken from next-auth/jwt:
- Visiting
/dashboardwithout a valid JWT cookie redirects to/sign-in. - Visiting
/sign-inor/sign-upwhile already authenticated redirects to/dashboard.
Accessing the Session in Server Components
Any Server Component or API route can read the full session (including custom fields) viagetServerSession:
NEXT_AUTH_CONFIG (the exported AuthOptions object from lib/auth.ts) as the argument — not the handler — so getServerSession can decrypt the token using the same secret and callbacks.