Skip to main content

Overview

A production-ready password-based authentication system for TanStack Start built on Convex Auth. Includes login, sign-up, email verification, password reset, and protected routes with full server-side and client-side implementations.

Features

  • Email and password authentication
  • Email verification via magic links
  • Password reset flow
  • Protected route patterns
  • Loading and error states
  • Form validation
  • Automatic redirects
  • Server-side auth checks
  • Type-safe with TypeScript

Installation

1

Install the component

npx shadcn@latest add https://convex-ui.vercel.app/r/password-based-auth-tanstack
2

Configure environment variables

Add to your .env file:
VITE_CONVEX_URL=https://your-project.convex.cloud
CONVEX_DEPLOYMENT=your-deployment-name
3

Set up email provider

In the Convex dashboard (Settings → Environment Variables), add:
AUTH_RESEND_KEY=re_your_resend_api_key
AUTH_EMAIL_FROM=[email protected]  # Optional
4

Start Convex dev server

npx convex dev
The schema and auth functions will be automatically deployed.

What Gets Installed

Components

  • login-form.tsx - Email/password login form
  • sign-up-form.tsx - User registration form
  • forgot-password-form.tsx - Request password reset
  • update-password-form.tsx - Set new password
  • logout-button.tsx - Sign out button

Routes

  • routes/auth/login.tsx - Login page
  • routes/auth/sign-up.tsx - Sign-up page
  • routes/auth/forgot-password.tsx - Password reset request
  • routes/auth/update-password.tsx - Reset password handler
  • routes/auth/sign-up-success.tsx - Email verification prompt
  • routes/_protected/index.tsx - Example protected route

Backend (Convex)

  • convex/schema.ts - Database schema for users
  • convex/auth.ts - Auth query endpoints
  • convex/auth.config.ts - Auth configuration
  • convex/users.ts - User queries
  • convex/http.ts - HTTP endpoints for auth callbacks

Environment Variables

Client (.env)

VITE_CONVEX_URL
string
required
Your Convex deployment URL
CONVEX_DEPLOYMENT
string
required
Your Convex deployment name

Server (Convex Dashboard)

AUTH_RESEND_KEY
string
required
Resend API key for sending verification and reset emails
AUTH_EMAIL_FROM
string
Sender email address (defaults to [email protected])

Usage

Basic Login Form

routes/auth/login.tsx
import { LoginForm } from "@/components/login-form";

export default function LoginPage() {
  return (
    <div className="flex min-h-screen items-center justify-center">
      <LoginForm />
    </div>
  );
}

Sign-Up Form

routes/auth/sign-up.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>
  );
}

Protected Routes

Create a route group with authentication checks:
routes/_protected.tsx
import { createFileRoute, redirect, Outlet } from "@tanstack/react-router";
import { useQuery } from "@tanstack/react-query";
import { convexQuery, api } from "@/lib/convex/server";

export const Route = createFileRoute("/_protected")(
  beforeLoad: async ({ context }) => {
    const user = await context.queryClient.fetchQuery(
      convexQuery(api.users.current, {})
    );
    
    if (!user) {
      throw redirect({ to: "/auth/login" });
    }
  },
  component: ProtectedLayout,
});

function ProtectedLayout() {
  return (
    <div>
      <nav>{/* Navigation */}</nav>
      <Outlet />
    </div>
  );
}

Logout Button

import { LogoutButton } from "@/components/logout-button";

export function Header() {
  return (
    <header>
      <h1>My App</h1>
      <LogoutButton />
    </header>
  );
}

Authentication Flow

1

Sign Up

User submits email and password → Account created → Verification email sent
2

Email Verification

User clicks link in email → Email verified → Can now sign in
3

Sign In

User submits credentials → Session created → Redirected to app
4

Password Reset

User requests reset → Email sent → Clicks link → Sets new password

API Reference

LoginForm Props

The login form is a standalone component with no required props:
import { LoginForm } from "@/components/login-form";

<LoginForm />
Features:
  • Email and password inputs
  • Client-side validation
  • Loading states
  • Error handling
  • Auto-redirect on success
  • Link to sign-up page
  • Link to forgot password

SignUpForm Props

The sign-up form is also standalone:
import { SignUpForm } from "@/components/sign-up-form";

<SignUpForm />
Features:
  • Email and password inputs
  • Confirm password field
  • Password strength indicator
  • Terms acceptance checkbox
  • Client-side validation
  • Auto-redirect to verification page

Auth Actions

Access auth methods using Convex Auth hooks:
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>;
}

Backend Implementation

Auth Configuration

The convex/auth.config.ts file configures password authentication:
import { AuthConfig } from "convex/server";

export default {
  providers: [
    {
      domain: process.env.CONVEX_SITE_URL!,
      applicationID: "convex",
    },
  ],
} satisfies AuthConfig;

User Queries

The convex/users.ts file provides user data access:
import { query } from "./_generated/server";
import { getAuthUserId } from "@convex-dev/auth/server";

// Get current authenticated user
export const current = query({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) return null;
    return await ctx.db.get(userId);
  },
});

Customization

Custom Email Templates

Modify email content in convex/auth.ts:
import { Resend } from "@resend/node";

const resend = new Resend(process.env.AUTH_RESEND_KEY);

export async function sendVerificationEmail(
  email: string,
  token: string
) {
  await resend.emails.send({
    from: process.env.AUTH_EMAIL_FROM ?? "[email protected]",
    to: email,
    subject: "Verify your email",
    html: `
      <h1>Welcome!</h1>
      <p>Click the link below to verify your email:</p>
      <a href="${process.env.CONVEX_SITE_URL}/auth/verify?token=${token}">
        Verify Email
      </a>
    `,
  });
}

Style Customization

The forms use shadcn/ui components. Customize via your theme:
<Card className="w-full max-w-md border-2 border-primary">
  <CardHeader className="bg-primary/5">
    {/* Styled header */}
  </CardHeader>
</Card>

Redirect Paths

Change redirect destinations in form components:
// In login-form.tsx
await signIn("password", formData);
navigate({ to: "/dashboard" }); // Changed from /protected

Security Best Practices

Always use HTTPS in production to protect credentials during transmission.
  • Passwords are hashed using industry-standard algorithms
  • Verification tokens expire after 24 hours
  • Reset tokens are single-use
  • Rate limiting should be added for production
  • CSRF protection is built-in

Troubleshooting

  • Check that AUTH_RESEND_KEY is set in Convex dashboard
  • Verify your Resend domain is configured
  • Check Convex logs for email errors
Ensure your app is wrapped with ConvexClientProvider from lib/convex/provider.tsx.
Make sure you’re using TanStack Router’s useNavigate hook correctly and the target route exists.
Run npx convex dev to generate type definitions from your schema.

Build docs developers (and LLMs) love