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 browserUser -> Login Page -> Convex Auth -> Session Token -> App
-> Mutation: create profile
-> Session managed by ConvexLogin 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
- Enable the provider in your Supabase dashboard under Authentication > Providers.
- Add the client ID and secret from the provider's developer console.
- Add a button in the login/register pages:
<Button onClick={() => signInWithOAuth("discord")}>
Continue with Discord
</Button>- Configure the provider in
convex/auth.config.ts. - Add the client ID and secret as environment variables.
- 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
- 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" });-
User clicks the email link, lands on the reset page.
-
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:
- Cancels Stripe subscription -- If the user has an active subscription, it is cancelled immediately via the Stripe API.
- Deletes user data -- Removes all user-related records from the database (profile, subscription, credits, etc.).
- Removes auth user -- Deletes the user from Supabase Auth using the service role client.
The deleteAccount Convex action performs the following steps in order:
- Cancels Stripe subscription -- If the user has an active subscription, it is cancelled immediately via the Stripe API.
- Deletes user data -- Removes all user-related records from the database (profile, subscription, credits, etc.).
- 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:
- Styling: Edit the form components in
apps/app/src/pages/auth/. - Fields: Add fields to the signup form -- they're stored in
raw_user_meta_data. - Redirect: Change post-login redirect in the auth callback handler.
- Email templates: Customize confirmation/reset emails in the Supabase dashboard under Authentication > Email Templates.
Done reading? Mark this page as complete.