Skip to main content

Overview

CodeJam uses @convex-dev/auth for authentication, providing multiple sign-in methods and session management.

Supported Providers

The authentication system supports four providers:
import { convexAuth } from "@convex-dev/auth/server";
import GitHub from "@auth/core/providers/github";
import Google from "@auth/core/providers/google";
import { Password } from "@convex-dev/auth/providers/Password";
import { Anonymous } from "@convex-dev/auth/providers/Anonymous";

export const { auth, signIn, signOut, store } = convexAuth({
  providers: [
    GitHub,
    Google,
    Password,
    Anonymous,
  ],
});

Provider Details

OAuth authentication via GitHub accounts. Users can sign in with their GitHub credentials.
  • Type: OAuth 2.0
  • Scopes: Basic profile information
  • User data: name, email, avatar
OAuth authentication via Google accounts.
  • Type: OAuth 2.0
  • Scopes: Profile and email
  • User data: name, email, avatar
Traditional email and password authentication.
  • Type: Credential-based
  • Security: Passwords are hashed and never stored in plain text
  • Verification: Optional email verification
Temporary anonymous sessions for users who want to try the platform without signing up.
  • Type: Guest sessions
  • Limitations: Cannot access social features (friends, battles)
  • Conversion: Can upgrade to full account later

Authentication Pattern

getAuthUserId

All authenticated backend functions use the getAuthUserId helper to retrieve the current user:
import { getAuthUserId } from "@convex-dev/auth/server";
import { query, mutation } from "./_generated/server";

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

Return Values

userId
Id<'users'> | null
Returns the user ID if authenticated, or null if not.

User Session Management

Heartbeat

The platform tracks user presence with a heartbeat system:
export const heartbeat = mutation({
  args: {},
  handler: async (ctx) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) return;
    await ctx.db.patch(userId, { lastSeen: Date.now() });
  },
});
Call this mutation periodically (e.g., every 30 seconds) to update the user’s lastSeen timestamp.

Anonymous User Restrictions

Anonymous users are restricted from social features:
async function checkNotAnonymous(ctx: any, userId: any) {
  const user = await ctx.db.get(userId);
  if (!user || user.isAnonymous) {
    return false;
  }
  return true;
}

export const sendFriendRequest = mutation({
  args: { targetId: v.id("users") },
  handler: async (ctx, args) => {
    const userId = await getAuthUserId(ctx);
    if (!userId) throw new Error("Unauthorized");

    const allowed = await checkNotAnonymous(ctx, userId);
    if (!allowed) {
      return { error: "Guest users cannot access social features. Please sign up." };
    }
    
    // ... rest of logic
  },
});

User Schema

Authenticated users are stored in the users table with the following fields:

Client-Side Usage

Sign In

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

function SignIn() {
  const { signIn } = useAuthActions();
  
  return (
    <div>
      <button onClick={() => signIn("github")}>Sign in with GitHub</button>
      <button onClick={() => signIn("google")}>Sign in with Google</button>
      <button onClick={() => signIn("anonymous")}>Continue as Guest</button>
    </div>
  );
}

Sign Out

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

function SignOut() {
  const { signOut } = useAuthActions();
  
  return <button onClick={() => signOut()}>Sign Out</button>;
}

Check Auth Status

import { useQuery } from "convex/react";
import { api } from "@/convex/_generated/api";

function Profile() {
  const user = useQuery(api.users.viewer);
  
  if (user === undefined) return <div>Loading...</div>;
  if (user === null) return <div>Please sign in</div>;
  
  return (
    <div>
      <h1>Welcome {user.name}!</h1>
      {user.isAnonymous && (
        <p>Sign up to unlock social features!</p>
      )}
    </div>
  );
}

Security Considerations

  • Never trust client data: Always use getAuthUserId(ctx) on the backend to verify authentication
  • Check anonymous status: Restrict social features for guest users
  • Validate permissions: Always verify the user has permission to access/modify data
  • Rate limiting: Consider implementing rate limits for sensitive operations

Build docs developers (and LLMs) love