Overview
A full-featured password authentication system built on Convex Auth. Includes:
- Email/password login and sign-up
- Email verification with OTP codes
- Password reset flow
- Strong password requirements
- Protected routes
- Optional OAuth providers (GitHub, Google)
Installation
npx shadcn@latest add https://convex-ui.vercel.app/r/password-based-auth-nextjs
This installs:
- Login, sign-up, and password reset forms
- Logout button component
- Protected route example
- Complete Convex backend with auth
- Convex client and provider
Post-Install Setup
Start Convex Development Server
Run the Convex dev server to initialize your deployment:This creates your Convex deployment and provides the deployment URL. Configure Environment Variables
Add to your .env.local:NEXT_PUBLIC_CONVEX_URL=https://your-deployment.convex.cloud
CONVEX_DEPLOYMENT=your-deployment-name
Set Up Email Provider
In the Convex dashboard (Settings > Environment Variables), add:Get a free Resend API key at resend.com. This is required for email verification and password reset. (Optional) Configure OAuth Providers
To enable GitHub or Google login, add to Convex dashboard:AUTH_GITHUB_ID=your-github-client-id
AUTH_GITHUB_SECRET=your-github-client-secret
AUTH_GOOGLE_ID=your-google-client-id
AUTH_GOOGLE_SECRET=your-google-client-secret
Configure callback URLs in provider dashboards:
- GitHub:
https://<your-convex-url>/api/auth/callback/github
- Google:
https://<your-convex-url>/api/auth/callback/google
Wrap App with Provider
Ensure your app is wrapped with the Convex provider:import { ConvexClientProvider } from "@/lib/convex/provider";
export default function RootLayout({ children }) {
return (
<html>
<body>
<ConvexClientProvider>{children}</ConvexClientProvider>
</body>
</html>
);
}
Components
Email and password login form with validation:
import { LoginForm } from "@/components/login-form";
export default function LoginPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<LoginForm />
</div>
);
}
Features:
- Email/password validation
- Loading states
- Error handling
- Link to sign-up page
- Link to forgot password
User registration with email verification:
app/auth/sign-up/page.tsx
import { SignUpForm } from "@/components/sign-up-form";
export default function SignUpPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<SignUpForm />
</div>
);
}
Features:
- Email validation
- Strong password requirements
- Email verification flow
- OTP code entry
Password reset request form:
app/auth/forgot-password/page.tsx
import { ForgotPasswordForm } from "@/components/forgot-password-form";
export default function ForgotPasswordPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<ForgotPasswordForm />
</div>
);
}
Set new password with OTP verification:
app/auth/update-password/page.tsx
import { UpdatePasswordForm } from "@/components/update-password-form";
export default function UpdatePasswordPage() {
return (
<div className="flex min-h-screen items-center justify-center">
<UpdatePasswordForm />
</div>
);
}
Simple logout button:
import { LogoutButton } from "@/components/logout-button";
export function Header() {
return (
<header>
<LogoutButton />
</header>
);
}
Authentication Hooks
useAuthActions
Handle authentication operations:
import { useAuthActions } from "@convex-dev/auth/react";
function MyComponent() {
const { signIn, signOut } = useAuthActions();
const handleLogin = async () => {
const formData = new FormData();
formData.set("email", "[email protected]");
formData.set("password", "password123");
formData.set("flow", "signIn");
await signIn("password", formData);
};
return <button onClick={handleLogin}>Sign In</button>;
}
useCurrentUser
Access the current authenticated user:
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
function UserProfile() {
const user = useQuery(api.users.current);
if (!user) return <div>Not logged in</div>;
return <div>Welcome, {user.name}!</div>;
}
Backend Configuration
The backend is configured in convex/auth.ts:
import { convexAuth } from "@convex-dev/auth/server";
import { Password } from "@convex-dev/auth/providers/Password";
import Resend from "@auth/core/providers/resend";
import GitHub from "@auth/core/providers/github";
import Google from "@auth/core/providers/google";
// Custom password provider with email verification
const CustomPassword = Password({
verify: ResendOTP,
reset: ResendOTPPasswordReset,
validatePasswordRequirements: (password: string) => {
if (password.length < 8) {
throw new Error("Password must be at least 8 characters");
}
if (!/[a-z]/.test(password)) {
throw new Error("Password must contain a lowercase letter");
}
if (!/[A-Z]/.test(password)) {
throw new Error("Password must contain an uppercase letter");
}
if (!/[0-9]/.test(password)) {
throw new Error("Password must contain a number");
}
},
});
export const { auth, signIn, signOut } = convexAuth({
providers: [CustomPassword, GitHub, Google],
});
Password Requirements
Passwords must contain:
- Minimum 8 characters
- At least one lowercase letter
- At least one uppercase letter
- At least one number
Protected Routes
Protect routes using server-side checks:
import { redirect } from "next/navigation";
import { fetchQuery } from "@/lib/convex/server";
import { api } from "@/convex/_generated/api";
export default async function ProtectedPage() {
const user = await fetchQuery(api.users.current);
if (!user) {
redirect("/auth/login");
}
return <div>Protected content</div>;
}
Or use client-side protection:
"use client";
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
import { redirect } from "next/navigation";
export default function ProtectedPage() {
const user = useQuery(api.users.current);
if (user === undefined) return <div>Loading...</div>;
if (user === null) redirect("/auth/login");
return <div>Protected content</div>;
}
Environment Variables
Client (.env.local)
Your Convex deployment URL
Your Convex deployment name
Server (Convex Dashboard)
Resend API key for sending email verification and reset codes
GitHub OAuth client ID (optional)
GitHub OAuth client secret (optional)
Google OAuth client ID (optional)
Google OAuth client secret (optional)
Email Verification Flow
- User signs up with email and password
- System sends 8-digit OTP to user’s email
- User enters OTP code
- Account is verified and user is logged in
OTP codes expire after 15 minutes.
Password Reset Flow
- User requests password reset
- System sends 8-digit OTP to user’s email
- User enters OTP and new password
- Password is updated and user is logged in