| name | convex-authentication |
| description | Set up and manage user authentication using Convex Auth with login, signup, password reset, and user profile initialization. Use when implementing auth flows, managing user sessions, initializing user profiles, or handling authentication state. |
| compatibility | Requires Convex Auth 0.0.90+, Next.js App Router, ConvexAuthNextjsProvider |
| metadata | {"author":"PictionAI","category":"backend, auth","frameworks":"Convex, @convex-dev/auth, Next.js 16"} |
Convex Authentication
Overview
This skill implements complete user authentication using Convex Auth built-in system, including signup with profile initialization, login, password reset, and session management integrated with Next.js 16.
Architecture
Auth Flow
1. User signs up with email + password
↓
2. Profile initialized in users table
(username, email, avatar_url, total_score, games_played)
↓
3. Auth token created
↓
4. User logged in, can access protected routes
↓
5. Can login/logout, update password
Convex Auth Integration
Note: Uses @convex-dev/auth 0.0.90, NOT custom auth.
import { ConvexAuth } from "@convex-dev/auth/server";
import { password } from "@convex-dev/auth/providers";
export const auth = new ConvexAuth({
providers: [password],
});
Database Schema
export const users = defineTable({
username: v.string(),
avatar_url: v.optional(v.string()),
total_score: v.number(),
games_played: v.number(),
created_at: v.number(),
})
.index("by_email", ["email"])
.index("by_username", ["username"]);
Authentication Provider Setup
Convex Auth Config
import { defineAuth } from "@convex-dev/auth/server";
import { password } from "@convex-dev/auth/providers";
export default defineAuth({
providers: [
password({
minPasswordLength: 8,
maxPasswordLength: 128,
}),
],
callbacks: {
async onSignUp(req) {
return req.identity;
},
async onSignIn(req) {
return req.identity;
},
},
});
Signup Mutation with Profile Initialization
signUpUser Mutation
export const signUpUser = mutation({
args: {
email: v.string(),
password: v.string(),
username: v.string(),
},
handler: async (ctx, args) => {
if (args.username.length < 3 || args.username.length > 30) {
throw new Error("Username must be 3-30 characters");
}
const existing = await ctx.db
.query("users")
.withIndex("by_username", (q) => q.eq("username", args.username))
.first();
if (existing) {
throw new Error("Username already taken");
}
const userId = (await ctx.auth.getUserIdentity())?.tokenIdentifier;
(!userId) {
();
}
now = .();
ctx..(, {
: args.,
: args.,
: ,
: ,
: ,
: now,
});
{
: ,
userId,
: args.,
};
},
});
Zod Validation Schema
import { z } from "zod";
export const signUpSchema = z.object({
email: z.string().email("Invalid email address"),
password: z
.string()
.min(8, "Password must be at least 8 characters")
.max(128, "Password too long"),
username: z
.string()
.min(3, "Username must be at least 3 characters")
.max(30, "Username must be at most 30 characters")
.regex(
/^[a-zA-Z0-9_-]+$/,
"Username can only contain letters, numbers, underscore, hyphen"
),
});
export const loginSchema = z.object({
email: z.string().email("Invalid email address"),
password: z.string().min(1, "Password required"),
});
export const updatePasswordSchema = z.object({
currentPassword: z.string(),
newPassword: z
.()
.(, )
.(
.(pwd),
)
.( .(pwd), ),
});
Custom Hooks
useAuthenticatedUser
import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";
interface AuthProfile {
user_id: string;
username: string;
email: string;
avatar_url?: string;
total_score: number;
games_played: number;
}
export function useAuthenticatedUser() {
const profile = useQuery(api.queries.profiles.getCurrentUserProfile);
return {
profile: profile as AuthProfile | null | undefined,
isLoading: profile === undefined,
isAuthenticated: profile !== null && profile !== undefined,
};
}
export function useAuthContext() {
const profile = useAuthenticatedUser();
return {
userId: profile.profile?.user_id,
username: profile.?.,
: profile.?.,
: profile.,
: profile.,
};
}
getCurrentUserProfile Query
export const getCurrentUserProfile = query({
args: {},
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) {
return null;
}
const user = await ctx.db
.query("users")
.withIndex("by_email", (q) => q.eq("email", identity.email!))
.first();
return user
? {
user_id: user._id,
username: user.username,
email: identity.email,
avatar_url: user.avatar_url,
total_score: user.total_score,
games_played: user.games_played,
}
: null;
},
});
React Components
AuthProvider Wrapper
import { ConvexAuthNextjsProvider } from "@convex-dev/auth/nextjs";
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<html>
<body>
<ConvexAuthNextjsProvider>
<ConvexProvider>{children}</ConvexProvider>
</ConvexAuthNextjsProvider>
</body>
</html>
);
}
SignUp Form
"use client";
import { useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { signUpSchema } from "@/lib/schemas";
import { useConvexAuth } from "@convex-dev/auth/react";
export function SignUpForm() {
const form = useForm({
resolver: zodResolver(signUpSchema),
defaultValues: {
email: "",
password: "",
username: "",
},
});
const { signUp } = useConvexAuth();
async function onSubmit(values: z.infer<typeof signUpSchema>) {
try {
await signUp("password", {
email: values.email,
password: values.password,
username: values.username,
});
window.location. = ;
} (error) {
form.(, {
: error ? error. : ,
});
}
}
(
);
}
Login Form
export function LoginForm() {
const form = useForm({
resolver: zodResolver(loginSchema),
});
const { logIn } = useConvexAuth();
async function onSubmit(values: z.infer<typeof loginSchema>) {
try {
await logIn("password", {
email: values.email,
password: values.password,
});
window.location.href = "/gioca";
} catch (error) {
form.setError("email", {
message: "Invalid email or password",
});
}
}
return (
<form onSubmit={form.handleSubmit(onSubmit)}>
{/* Form fields */}
<button type="submit">Log In</button>
</form>
);
}
Session Management
Check Auth Status
export function ProtectedRoute({ children }: { children: React.ReactNode }) {
const { isAuthenticated, isLoading } = useAuthenticatedUser();
if (isLoading) {
return <LoadingSpinner />;
}
if (!isAuthenticated) {
return <Redirect to="/auth/login" />;
}
return <>{children}</>;
}
Logout
export function LogoutButton() {
const { logOut } = useConvexAuth();
const handleLogout = async () => {
await logOut();
window.location.href = "/";
};
return <button onClick={handleLogout}>Logout</button>;
}
Password Reset Flow
Request Password Reset
export const requestPasswordReset = mutation({
args: { email: v.string() },
handler: async (ctx, args) => {
const user = await ctx.db
.query("users")
.withIndex("by_email", (q) => q.eq("email", args.email))
.first();
if (!user) {
return { success: true };
}
return { success: true };
},
});
Update Password
export const updatePassword = mutation({
args: {
currentPassword: v.string(),
newPassword: v.string(),
},
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error("Not authenticated");
return { success: true };
},
});
Environment Variables
CONVEX_DEPLOYMENT=dev
NEXT_PUBLIC_CONVEX_URL=https://xxx.convex.cloud
CONVEX_AUTH_CALLBACK=http://localhost:3000
Common Patterns
Get current user ID
const { userId } = useAuthContext();
Redirect non-authenticated users
useEffect(() => {
if (!isAuthenticated && !isLoading) {
router.push("/auth/login");
}
}, [isAuthenticated, isLoading, router]);
Update user score after game
const updateScore = useMutation(api.mutations.profiles.updateUserScore);
await updateScore({
user_id: userId,
points: 150,
});
Best Practices
✅ Always validate email format server-side
✅ Hash passwords (Convex Auth does this automatically)
✅ Check username uniqueness before signup
✅ Use protected routes for authenticated pages
✅ Log auth events for security monitoring
✅ Implement rate limiting on auth endpoints
✅ Use secure HTTP-only cookies for tokens
✅ Validate password strength requirements
See Also
convex/auth.ts - Auth configuration
convex/auth.config.ts - Auth providers setup
convex/queries/profiles.ts - User profile queries
components/auth/ - Auth UI components
- Convex Auth docs: https://docs.convex.dev/auth/overview