Skip to main content

Authentication Guide

Learn how to implement secure authentication in your TrendGate applications.

Overview​

TrendGate uses Clerk for authentication and user management. This provides:

  • Multiple auth methods - Email/password, Google, Apple, and more
  • Session management - Secure, scalable session handling
  • Multi-factor authentication - Enhanced security options
  • Role-based access control - Fine-grained permissions
  • User profiles - Customizable user metadata

Getting Started​

1. Environment Setup​

Configure your environment variables:

# .env.local
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Optional: Custom paths
NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in
NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up
NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard
NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/onboarding

2. Initialize Clerk​

Wrap your app with ClerkProvider:

// app/layout.tsx
import { ClerkProvider } from "@clerk/nextjs";

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<ClerkProvider>
<html lang="en">
<body>{children}</body>
</html>
</ClerkProvider>
);
}

3. Protect Routes​

Use middleware to protect routes:

// middleware.ts
import { authMiddleware } from "@clerk/nextjs";

export default authMiddleware({
publicRoutes: ["/", "/api/webhooks(.*)", "/sign-in", "/sign-up"],
ignoredRoutes: ["/api/public(.*)"],
});

export const config = {
matcher: ["/((?!.+\\.[\\w]+$|_next).*)", "/", "/(api|trpc)(.*)"],
};

User Roles & Permissions​

Define Roles​

TrendGate uses a role-based permission system:

export enum UserRole {
ADMIN = "admin",
INSTRUCTOR = "instructor",
ACCOUNT_OWNER = "account_owner",
MEMBER = "member",
}

export const rolePermissions = {
[UserRole.ADMIN]: ["manage_users", "manage_instructors", "view_analytics", "manage_settings"],
[UserRole.INSTRUCTOR]: [
"manage_sessions",
"view_bookings",
"manage_availability",
"view_earnings",
],
[UserRole.ACCOUNT_OWNER]: ["book_sessions", "manage_members", "view_progress", "manage_payments"],
[UserRole.MEMBER]: ["view_sessions", "view_progress"],
};

Set User Roles​

Update user metadata during onboarding:

// app/api/users/update-role/route.ts
import { currentUser, clerkClient } from "@clerk/nextjs";

export async function POST(req: Request) {
const user = await currentUser();
if (!user) {
return new Response("Unauthorized", { status: 401 });
}

const { role } = await req.json();

await clerkClient.users.updateUserMetadata(user.id, {
publicMetadata: {
role,
},
});

return new Response("OK");
}

Check Permissions​

Create a permission checking utility:

// lib/auth.ts
import { currentUser } from "@clerk/nextjs";

export async function checkPermission(permission: string): Promise<boolean> {
const user = await currentUser();
if (!user) return false;

const role = user.publicMetadata.role as UserRole;
if (!role) return false;

return rolePermissions[role]?.includes(permission) || false;
}

// Usage in server components
export default async function AdminPage() {
const hasPermission = await checkPermission("manage_users");

if (!hasPermission) {
redirect("/dashboard");
}

return <AdminDashboard />;
}

Client-Side Authentication​

useUser Hook​

Access user data in client components:

"use client";

import { useUser } from "@clerk/nextjs";

export function UserProfile() {
const { isLoaded, isSignedIn, user } = useUser();

if (!isLoaded) {
return <div>Loading...</div>;
}

if (!isSignedIn) {
return <div>Not signed in</div>;
}

return (
<div>
<h1>Welcome, {user.firstName}!</h1>
<p>Role: {user.publicMetadata.role}</p>
</div>
);
}

SignIn/SignUp Components​

Use Clerk's pre-built components:

// app/sign-in/[[...sign-in]]/page.tsx
import { SignIn } from "@clerk/nextjs";

export default function SignInPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignIn
appearance={{
elements: {
formButtonPrimary: "bg-primary hover:bg-primary/90 text-sm",
},
}}
/>
</div>
);
}

API Authentication​

Protect API Routes​

// app/api/protected/route.ts
import { auth } from "@clerk/nextjs";

export async function GET() {
const { userId } = auth();

if (!userId) {
return new Response("Unauthorized", { status: 401 });
}

// Your protected logic here
return Response.json({ message: "Protected data" });
}

tRPC Authentication​

Integrate with tRPC:

// server/api/trpc.ts
import { auth } from "@clerk/nextjs";
import { TRPCError } from "@trpc/server";

export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { userId } = auth();

return {
...opts,
userId,
};
};

const enforceUserIsAuthed = t.middleware(async ({ ctx, next }) => {
if (!ctx.userId) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}

return next({
ctx: {
userId: ctx.userId,
},
});
});

export const protectedProcedure = t.procedure.use(enforceUserIsAuthed);

OAuth Providers​

Configure OAuth​

Enable OAuth providers in Clerk Dashboard:

  1. Navigate to User & Authentication → Social Connections
  2. Enable desired providers (Google, Apple, etc.)
  3. Configure OAuth credentials

Custom OAuth Flow​

Handle OAuth callbacks:

// app/api/auth/callback/route.ts
export async function GET(req: Request) {
const { searchParams } = new URL(req.url);
const code = searchParams.get("code");

if (!code) {
return new Response("Missing code", { status: 400 });
}

// Exchange code for tokens
// Create or update user
// Set custom metadata

return redirect("/dashboard");
}

Session Management​

Access Session Data​

import { auth } from "@clerk/nextjs";

export async function getSessionData() {
const { sessionId, userId, sessionClaims } = auth();

return {
sessionId,
userId,
role: sessionClaims?.role,
permissions: sessionClaims?.permissions,
};
}

Custom Session Claims​

Add custom data to sessions:

// Set custom claims when creating session
await clerkClient.sessions.updateSession(sessionId, {
publicUserData: {
role: "instructor",
instructorId: "inst_123",
},
});

Security Best Practices​

  1. Environment Variables - Never commit secrets to version control
  2. HTTPS Only - Always use HTTPS in production
  3. Session Expiry - Configure appropriate session timeouts
  4. Rate Limiting - Implement rate limiting on auth endpoints
  5. Audit Logs - Track authentication events

Testing Authentication​

Mock Authentication in Tests​

// tests/helpers/auth.ts
import { SignJWT } from "jose";

export async function mockAuth(userId: string, role: UserRole) {
const token = await new SignJWT({
userId,
role,
})
.setProtectedHeader({ alg: "HS256" })
.setExpirationTime("2h")
.sign(new TextEncoder().encode(process.env.CLERK_SECRET_KEY!));

return token;
}

// In tests
const token = await mockAuth("user_123", UserRole.INSTRUCTOR);

Webhooks​

Handle Clerk webhook events:

// app/api/webhooks/clerk/route.ts
import { Webhook } from "svix";
import { headers } from "next/headers";
import { WebhookEvent } from "@clerk/nextjs/server";

export async function POST(req: Request) {
const WEBHOOK_SECRET = process.env.CLERK_WEBHOOK_SECRET;

if (!WEBHOOK_SECRET) {
throw new Error("Missing CLERK_WEBHOOK_SECRET");
}

// Verify webhook signature
const headerPayload = headers();
const svix_id = headerPayload.get("svix-id");
const svix_timestamp = headerPayload.get("svix-timestamp");
const svix_signature = headerPayload.get("svix-signature");

const body = await req.text();
const wh = new Webhook(WEBHOOK_SECRET);

let evt: WebhookEvent;

try {
evt = wh.verify(body, {
"svix-id": svix_id!,
"svix-timestamp": svix_timestamp!,
"svix-signature": svix_signature!,
}) as WebhookEvent;
} catch (err) {
return new Response("Error verifying webhook", { status: 400 });
}

// Handle events
switch (evt.type) {
case "user.created":
await handleUserCreated(evt.data);
break;
case "user.updated":
await handleUserUpdated(evt.data);
break;
case "session.created":
await handleSessionCreated(evt.data);
break;
}

return new Response("OK", { status: 200 });
}

Troubleshooting​

Common Issues​

  1. Invalid API Keys

    • Verify keys in .env.local
    • Check Clerk dashboard for correct keys
  2. Middleware Not Working

    • Ensure middleware.ts is in root directory
    • Check matcher configuration
  3. Session Not Persisting

    • Verify cookie settings
    • Check domain configuration
  4. OAuth Redirect Issues

    • Update redirect URLs in OAuth provider
    • Check Clerk dashboard settings