Skip to main content

Authentication Setup Guide

This guide will walk you through setting up Clerk.js authentication for Akako LMS.

Prerequisites

  • Clerk.js account (sign up here)
  • PostgreSQL database
  • Node.js 18+ environment

Step 1: Create Clerk Application

  1. Sign up for Clerk.js:

    • Go to clerk.com
    • Create a new account or sign in
  2. Create a new application:

    • Click "Create Application"
    • Choose "Next.js" as your framework
    • Select your preferred authentication methods (Email, Google, etc.)
  3. Configure your application:

    • Set your application name (e.g., "Akako LMS")
    • Choose your preferred sign-in methods
    • Configure user profile fields

Step 2: Get Clerk Credentials

After creating your application, you'll need these credentials:

  1. Go to API Keys:

    • Navigate to "API Keys" in your Clerk dashboard
    • Copy the following keys:
      • NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY
      • CLERK_SECRET_KEY
  2. Get Webhook Endpoints (optional):

    • Go to "Webhooks" section
    • Create webhook endpoints for user synchronization

Step 3: Environment Configuration

Add the following environment variables to your .env.local file:

# Clerk.js Configuration
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

# Database
DATABASE_URL="postgresql://username:password@localhost:5432/akako"

# Application URLs
NEXT_PUBLIC_APP_URL=http://localhost:3000

Step 4: Install Dependencies

Install the required Clerk.js packages:

pnpm add @clerk/nextjs

Step 5: Configure Next.js

1. Update next.config.js

/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
images: {
domains: ['img.clerk.com'],
},
};

module.exports = nextConfig;

2. Create Middleware

Create middleware.ts in your project root:

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

export default authMiddleware({
publicRoutes: [
"/",
"/api/webhooks(.*)",
"/api/auth/sync",
],
ignoredRoutes: [
"/api/webhooks/clerk",
],
});

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

3. Wrap App with ClerkProvider

Update your root layout (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>
);
}

Step 6: Database Setup

1. Update Prisma Schema

Ensure your prisma/schema.prisma includes the Clerk integration:

model User {
id String @id @default(cuid())
clerkId String @unique // Clerk user ID
email String @unique
firstName String?
lastName String?
// ... other fields

userRoles UserRole[]
}

model UserRole {
id String @id @default(cuid())
userId String
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
role Role

effectiveStartDate DateTime @default(now())
effectiveEndDate DateTime?
isActive Boolean @default(true)

createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}

enum Role {
LEARNER
MENTOR
ADMIN
}

2. Run Database Migration

pnpm prisma generate
pnpm prisma db push

Step 7: Create Authentication Components

1. Sign In Component

Create components/auth/SignIn.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-[#07294d] hover:bg-[#051f3a]',
card: 'shadow-lg',
},
}}
/>
</div>
);
}

2. Sign Up Component

Create components/auth/SignUp.tsx:

import { SignUp } from '@clerk/nextjs';

export default function SignUpPage() {
return (
<div className="flex items-center justify-center min-h-screen">
<SignUp
appearance={{
elements: {
formButtonPrimary: 'bg-[#07294d] hover:bg-[#051f3a]',
card: 'shadow-lg',
},
}}
/>
</div>
);
}

Step 8: User Synchronization

1. Create Sync API Route

Create app/api/auth/sync/route.ts:

import { NextRequest, NextResponse } from 'next/server';
import { prisma } from '@/lib/prisma';
import { Role } from '@prisma/client';

export async function POST(req: NextRequest) {
try {
const { userId, email, firstName, lastName } = await req.json();

const user = await prisma.user.upsert({
where: { clerkId: userId },
update: {
email,
firstName,
lastName,
},
create: {
clerkId: userId,
email,
firstName,
lastName,
userRoles: {
create: {
role: Role.LEARNER,
effectiveStartDate: new Date(),
},
},
},
});

return NextResponse.json(user);
} catch (error) {
console.error('User sync error:', error);
return NextResponse.json(
{ error: 'Failed to sync user' },
{ status: 500 }
);
}
}

2. Create User Profile Hook

Create lib/hooks/useAuth.ts:

import { useUser } from '@clerk/nextjs';
import { useQuery } from '@tanstack/react-query';

export function useAuth() {
const { user: clerkUser, isLoaded } = useUser();

const { data: user, isLoading } = useQuery({
queryKey: ['user', clerkUser?.id],
queryFn: async () => {
const response = await fetch('/api/user/profile');
if (!response.ok) throw new Error('Failed to fetch user');
return response.json();
},
enabled: isLoaded && !!clerkUser,
});

return {
user,
isLoading: isLoading || !isLoaded,
isAuthenticated: !!user,
roles: user?.roles || [],
};
}

Step 9: Test Authentication

1. Start Development Server

pnpm dev

2. Test Sign Up/Sign In

  1. Navigate to http://localhost:3000
  2. Click "Sign Up" to create a new account
  3. Verify user is created in your database
  4. Test sign in functionality

3. Verify Database

Check your PostgreSQL database to ensure:

  • User record is created with clerkId
  • Default LEARNER role is assigned
  • User profile data is synced
-- Check users table
SELECT id, "clerkId", email, "firstName", "lastName" FROM users;

-- Check user roles
SELECT u.email, ur.role, ur."effectiveStartDate"
FROM users u
JOIN user_roles ur ON u.id = ur."userId";

Step 10: Configure Webhooks (Optional)

For automatic user synchronization, set up Clerk webhooks:

1. Create Webhook Endpoint

Create app/api/webhooks/clerk/route.ts:

import { NextRequest, NextResponse } from 'next/server';
import { Webhook } from 'svix';
import { prisma } from '@/lib/prisma';
import { Role } from '@prisma/client';

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

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

const payload = await req.text();
const headers = req.headers;

const wh = new Webhook(WEBHOOK_SECRET);
const evt = wh.verify(payload, {
'svix-id': headers.get('svix-id'),
'svix-timestamp': headers.get('svix-timestamp'),
'svix-signature': headers.get('svix-signature'),
});

const { type, data } = evt;

if (type === 'user.created') {
await prisma.user.create({
data: {
clerkId: data.id,
email: data.email_addresses[0].email_address,
firstName: data.first_name,
lastName: data.last_name,
userRoles: {
create: {
role: Role.LEARNER,
effectiveStartDate: new Date(),
},
},
},
});
}

return NextResponse.json({ received: true });
}

2. Add Webhook Secret to Environment

CLERK_WEBHOOK_SECRET=whsec_...

3. Configure Webhook in Clerk Dashboard

  1. Go to "Webhooks" in your Clerk dashboard
  2. Create a new webhook endpoint
  3. Set URL to: https://yourdomain.com/api/webhooks/clerk
  4. Select events: user.created, user.updated, user.deleted

Troubleshooting

Common Issues

  1. "Invalid Clerk Key" Error:

    • Verify your environment variables are correct
    • Ensure keys match your Clerk application
  2. Database Connection Issues:

    • Check your DATABASE_URL format
    • Ensure PostgreSQL is running
    • Run pnpm prisma generate after schema changes
  3. User Not Syncing:

    • Check webhook configuration
    • Verify webhook secret is correct
    • Check API route logs for errors

Debug Mode

Enable Clerk debug mode for development:

NEXT_PUBLIC_CLERK_DEBUG=true

Next Steps