ScaleRocket/Web

Authentication

Auth setup with email/password, OAuth providers, session handling, and route protection.

Overview

ScaleRocket uses Supabase Auth for authentication. It supports email/password login, OAuth providers (Google, GitHub), password reset, and automatic session management.

Supabase handles all auth logic server-side. The client SDK manages tokens and session refresh automatically.

ScaleRocket uses Convex Auth for authentication. It supports email/password login, OAuth providers (Google, GitHub), and automatic session management.

Convex Auth is built on top of the @convex-dev/auth library and handles all auth logic server-side.

Auth pages live in apps/app/src/pages/auth/.

Auth Architecture

User -> Login Page -> Supabase Auth -> JWT Token -> App
                                    -> Trigger: create profile
                                    -> Session stored in browser
User -> Login Page -> Convex Auth -> Session Token -> App
                                  -> Mutation: create profile
                                  -> Session managed by Convex

Login and Register Pages

Register

Located at apps/app/src/pages/auth/register.tsx:

import { supabase } from "@/lib/supabase";

const { data, error } = await supabase.auth.signUp({
  email,
  password,
  options: {
    data: {
      full_name: name,
    },
  },
});

Located at apps/app/src/pages/auth/register.tsx:

import { useAuthActions } from "@convex-dev/auth/react";

const { signIn } = useAuthActions();

await signIn("password", { email, password, name, flow: "signUp" });

Login

Located at apps/app/src/pages/auth/login.tsx:

const { data, error } = await supabase.auth.signInWithPassword({
  email,
  password,
});

Located at apps/app/src/pages/auth/login.tsx:

import { useAuthActions } from "@convex-dev/auth/react";

const { signIn } = useAuthActions();

await signIn("password", { email, password, flow: "signIn" });

OAuth Providers

Google and GitHub

const { data, error } = await supabase.auth.signInWithOAuth({
  provider: "google", // or "github"
  options: {
    redirectTo: `${window.location.origin}/auth/callback`,
  },
});
import { useAuthActions } from "@convex-dev/auth/react";

const { signIn } = useAuthActions();

// Google
await signIn("google");

// GitHub
await signIn("github");

Adding a new OAuth provider

  1. Enable the provider in your Supabase dashboard under Authentication > Providers.
  2. Add the client ID and secret from the provider's developer console.
  3. Add a button in the login/register pages:
<Button onClick={() => signInWithOAuth("discord")}>
  Continue with Discord
</Button>
  1. Configure the provider in convex/auth.config.ts.
  2. Add the client ID and secret as environment variables.
  3. Add a button in the login/register pages:
<Button onClick={() => signIn("discord")}>
  Continue with Discord
</Button>

Auth Callback

The callback page at apps/app/src/pages/auth/callback.tsx exchanges the OAuth code for a session:

const { data, error } = await supabase.auth.exchangeCodeForSession(code);

Convex handles the OAuth callback automatically. No manual code exchange is needed -- the @convex-dev/auth library manages the callback flow.

Password Reset Flow

  1. User requests a reset from the login page:
await supabase.auth.resetPasswordForEmail(email, {
  redirectTo: `${window.location.origin}/auth/reset-password`,
});
import { useAuthActions } from "@convex-dev/auth/react";

const { signIn } = useAuthActions();

await signIn("password", { email, flow: "reset" });
  1. User clicks the email link, lands on the reset page.

  2. User sets a new password:

await supabase.auth.updateUser({ password: newPassword });
await signIn("password", { email, password: newPassword, code, flow: "reset-verification" });

Session Handling

The Supabase client automatically manages sessions:

// Get current session
const { data: { session } } = await supabase.auth.getSession();

// Listen for auth state changes
supabase.auth.onAuthStateChange((event, session) => {
  if (event === "SIGNED_IN") {
    // redirect to dashboard
  }
  if (event === "SIGNED_OUT") {
    // redirect to login
  }
});

Sessions are stored in localStorage and refreshed automatically before expiry.

Convex manages sessions automatically via the ConvexAuthProvider:

import { useConvexAuth } from "convex/react";

const { isAuthenticated, isLoading } = useConvexAuth();

// On the server side (in Convex functions):
import { getAuthUserId } from "@convex-dev/auth/server";

const userId = await getAuthUserId(ctx);

Sessions are managed server-side by Convex. No manual token management is needed.

Route Protection

Protect routes using an auth guard component:

// apps/app/src/components/auth-guard.tsx
import { useAuth } from "@/hooks/use-auth";
import { Navigate } from "react-router-dom";

export function AuthGuard({ children }: { children: React.ReactNode }) {
  const { user, loading } = useAuth();

  if (loading) return <LoadingSpinner />;
  if (!user) return <Navigate to="/auth/login" />;

  return children;
}

Protect routes using the Convex auth hook:

// apps/app/src/components/auth-guard.tsx
import { useConvexAuth } from "convex/react";
import { Navigate } from "react-router-dom";

export function AuthGuard({ children }: { children: React.ReactNode }) {
  const { isAuthenticated, isLoading } = useConvexAuth();

  if (isLoading) return <LoadingSpinner />;
  if (!isAuthenticated) return <Navigate to="/auth/login" />;

  return children;
}

Wrap protected routes:

<Route
  path="/dashboard"
  element={
    <AuthGuard>
      <Dashboard />
    </AuthGuard>
  }
/>

The useAuth Hook

// apps/app/src/hooks/use-auth.ts
export function useAuth() {
  const [user, setUser] = useState<User | null>(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
      setLoading(false);
    });

    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null);
      }
    );

    return () => subscription.unsubscribe();
  }, []);

  return { user, loading };
}
// apps/app/src/hooks/use-auth.ts
import { useConvexAuth } from "convex/react";
import { useQuery } from "convex/react";
import { api } from "../../convex/_generated/api";

export function useAuth() {
  const { isAuthenticated, isLoading } = useConvexAuth();
  const user = useQuery(api.users.getMe);

  return {
    user: isAuthenticated ? user : null,
    loading: isLoading,
  };
}

Account Deletion

ScaleRocket includes account deletion functionality that handles full account cleanup. Users can delete their account from Settings > Danger Zone in the dashboard.

How it works

The delete-account Edge Function performs the following steps in order:

  1. Cancels Stripe subscription -- If the user has an active subscription, it is cancelled immediately via the Stripe API.
  2. Deletes user data -- Removes all user-related records from the database (profile, subscription, credits, etc.).
  3. Removes auth user -- Deletes the user from Supabase Auth using the service role client.

The deleteAccount Convex action performs the following steps in order:

  1. Cancels Stripe subscription -- If the user has an active subscription, it is cancelled immediately via the Stripe API.
  2. Deletes user data -- Removes all user-related records from the database (profile, subscription, credits, etc.).
  3. Removes auth user -- Deletes the user session and auth data.

Usage

The function requires a confirmEmail parameter that must match the user's email address to prevent accidental deletion:

const { data, error } = await supabase.functions.invoke("delete-account", {
  body: {
    confirmEmail: user.email,
  },
});

If the provided email does not match the authenticated user's email, the function returns a 400 error.

The mutation requires a confirmEmail parameter that must match the user's email address to prevent accidental deletion:

import { useMutation } from "convex/react";
import { api } from "../../convex/_generated/api";

const deleteAccount = useMutation(api.users.deleteAccount);

await deleteAccount({ confirmEmail: user.email });

Customizing Auth Pages

Auth pages use components from packages/ui. To customize:

  1. Styling: Edit the form components in apps/app/src/pages/auth/.
  2. Fields: Add fields to the signup form -- they're stored in raw_user_meta_data.
  3. Redirect: Change post-login redirect in the auth callback handler.
  4. Email templates: Customize confirmation/reset emails in the Supabase dashboard under Authentication > Email Templates.

Done reading? Mark this page as complete.

On this page