import { query, mutation } from './_generated/server';
// Check authentication
export const getCurrentUser = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return null;
return {
id: identity.tokenIdentifier,
subject: identity.subject,
issuer: identity.issuer,
name: identity.name,
email: identity.email,
emailVerified: identity.emailVerified,
};
},
});
// Require authentication
export const createPost = mutation({
args: { title: v.string(), body: v.string() },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error('Not authenticated');
return await ctx.db.insert('posts', {
title: args.title,
body: args.body,
authorId: identity.tokenIdentifier,
authorName: identity.name ?? 'Anonymous',
});
},
});
// Authorization by role
export const deleteUser = mutation({
args: { userId: v.id('users') },
handler: async (ctx, args) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error('Not authenticated');
const role = identity.role as string | undefined;
if (role !== 'admin') throw new Error('Not authorized');
await ctx.db.delete(args.userId);
},
});interface UserIdentity {
tokenIdentifier: string; // {issuer}|{subject}
subject: string; // Unique user ID within issuer
issuer: string; // Auth provider URL
name?: string;
email?: string;
emailVerified?: boolean;
phoneNumber?: string;
phoneNumberVerified?: boolean;
pictureUrl?: string;
givenName?: string;
familyName?: string;
nickname?: string;
updatedAt?: number;
[key: string]: unknown; // Custom claims
}// Store user on first login
export const syncUser = mutation({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) throw new Error('Not authenticated');
const existing = await ctx.db
.query('users')
.withIndex('by_token_id', q => q.eq('tokenIdentifier', identity.tokenIdentifier))
.unique();
if (existing) {
await ctx.db.patch(existing._id, {
name: identity.name,
email: identity.email,
lastSeen: Date.now(),
});
return existing._id;
} else {
return await ctx.db.insert('users', {
tokenIdentifier: identity.tokenIdentifier,
name: identity.name ?? 'Anonymous',
email: identity.email,
createdAt: Date.now(),
});
}
},
});
// Get user-specific data
export const getMyPosts = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (!identity) return [];
return await ctx.db
.query('posts')
.withIndex('by_author', q => q.eq('authorId', identity.tokenIdentifier))
.collect();
},
});
// Optional authentication
export const listPosts = query({
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
if (identity) {
// Show all posts for authenticated users
return await ctx.db.query('posts').collect();
} else {
// Show only public posts
return await ctx.db.query('posts')
.filter(q => q.eq(q.field('isPublic'), true))
.collect();
}
},
});// Setup
import { ConvexProviderWithClerk } from 'convex/react-clerk';
import { ConvexReactClient } from 'convex/react';
import { ClerkProvider, useAuth } from '@clerk/clerk-react';
const convex = new ConvexReactClient(process.env.REACT_APP_CONVEX_URL!);
function App() {
return (
<ClerkProvider publishableKey={process.env.REACT_APP_CLERK_KEY!}>
<ConvexProviderWithClerk client={convex} useAuth={useAuth}>
<YourApp />
</ConvexProviderWithClerk>
</ClerkProvider>
);
}
// Use Clerk hooks
import { useUser } from '@clerk/clerk-react';
import { SignInButton, SignOutButton } from '@clerk/clerk-react';
function Profile() {
const { isSignedIn, user } = useUser();
if (!isSignedIn) return <SignInButton />;
return (
<div>
<h1>Welcome, {user.firstName}</h1>
<SignOutButton />
</div>
);
}// Setup
import { ConvexProviderWithAuth0 } from 'convex/react-auth0';
import { ConvexReactClient } from 'convex/react';
import { Auth0Provider } from '@auth0/auth0-react';
const convex = new ConvexReactClient(process.env.REACT_APP_CONVEX_URL!);
function App() {
return (
<Auth0Provider
domain={process.env.REACT_APP_AUTH0_DOMAIN!}
clientId={process.env.REACT_APP_AUTH0_CLIENT_ID!}
authorizationParams={{
redirect_uri: window.location.origin,
}}
>
<ConvexProviderWithAuth0 client={convex}>
<YourApp />
</ConvexProviderWithAuth0>
</Auth0Provider>
);
}
// Use Auth0 hooks
import { useAuth0 } from '@auth0/auth0-react';
function Profile() {
const { isAuthenticated, user, loginWithRedirect, logout } = useAuth0();
if (!isAuthenticated) {
return <button onClick={() => loginWithRedirect()}>Log In</button>;
}
return (
<div>
<h1>Welcome, {user?.name}</h1>
<button onClick={() => logout()}>Log Out</button>
</div>
);
}import { Authenticated, Unauthenticated, AuthLoading } from 'convex/react';
function App() {
return (
<div>
<AuthLoading>
<div>Loading authentication...</div>
</AuthLoading>
<Authenticated>
<Dashboard />
<LogoutButton />
</Authenticated>
<Unauthenticated>
<LandingPage />
<LoginButton />
</Unauthenticated>
</div>
);
}
// Navigation
function Navigation() {
return (
<nav>
<a href="/">Home</a>
<Authenticated>
<a href="/dashboard">Dashboard</a>
<a href="/profile">Profile</a>
</Authenticated>
<Unauthenticated>
<a href="/about">About</a>
</Unauthenticated>
</nav>
);
}import { ConvexProviderWithAuth, ConvexReactClient } from 'convex/react';
const convex = new ConvexReactClient(process.env.REACT_APP_CONVEX_URL!);
function useMyAuth() {
const [isLoading, setIsLoading] = useState(true);
const [isAuthenticated, setIsAuthenticated] = useState(false);
const fetchAccessToken = async ({ forceRefreshToken }) => {
// Your logic to get JWT token
return await myAuthService.getToken(forceRefreshToken);
};
useEffect(() => {
// Initialize auth state
myAuthService.onAuthChange((authenticated) => {
setIsAuthenticated(authenticated);
setIsLoading(false);
});
}, []);
return { isLoading, isAuthenticated, fetchAccessToken };
}
function App() {
return (
<ConvexProviderWithAuth client={convex} useAuth={useMyAuth}>
<YourApp />
</ConvexProviderWithAuth>
);
}import { auth } from '@clerk/nextjs';
import { fetchQuery, preloadQuery } from 'convex/nextjs';
import { api } from '@/convex/_generated/api';
// Fetch with auth
export default async function PrivatePage() {
const { getToken } = auth();
const token = await getToken({ template: 'convex' });
const userData = await fetchQuery(
api.users.getCurrent,
{},
{ token }
);
return <div>Welcome, {userData.name}</div>;
}
// Preload with auth
export default async function Dashboard() {
const { getToken } = auth();
const token = await getToken({ template: 'convex' });
const preloadedData = await preloadQuery(
api.dashboard.getData,
{},
{ token }
);
return <DashboardClient preloaded={preloadedData} />;
}
// Server Action with auth
async function updateProfile(formData: FormData) {
'use server';
const { getToken } = auth();
const token = await getToken({ template: 'convex' });
const bio = formData.get('bio') as string;
await fetchMutation(
api.users.updateProfile,
{ bio },
{ token }
);
revalidatePath('/profile');
}// convex/auth.config.ts
// Clerk
export default {
providers: [
{
domain: 'https://your-domain.clerk.accounts.dev',
applicationID: 'YOUR_CLERK_APP_ID',
},
],
};
// Auth0
export default {
providers: [
{
domain: 'https://your-domain.auth0.com',
applicationID: 'YOUR_AUTH0_CLIENT_ID',
},
],
};
// Custom JWT
export default {
providers: [
{
type: 'customJwt',
issuer: 'https://your-auth-server.com',
jwks: 'https://your-auth-server.com/.well-known/jwks.json',
algorithm: 'RS256',
},
],
};
// Multiple providers
export default {
providers: [
{
domain: 'https://your-domain.auth0.com',
applicationID: 'YOUR_AUTH0_CLIENT_ID',
},
{
domain: 'https://accounts.google.com',
applicationID: 'YOUR_GOOGLE_CLIENT_ID',
},
],
};import { ConvexReactClient } from 'convex/react';
const convex = new ConvexReactClient(process.env.REACT_APP_CONVEX_URL!);
// Set auth token
convex.setAuth(async () => {
const token = await myAuthService.getToken();
return token;
});
// Clear auth
convex.clearAuth();
// With Firebase Auth
import { getAuth, onAuthStateChanged } from 'firebase/auth';
const auth = getAuth();
onAuthStateChanged(auth, async (user) => {
if (user) {
convex.setAuth(async () => await user.getIdToken());
} else {
convex.clearAuth();
}
});