Skip to main content

Overview

SkillRise uses Clerk for authentication and user management. Clerk handles user sign-up, sign-in, profile management, and session management. User data is synced to your MongoDB database via webhooks.

Features

  • Authentication: Email/password, OAuth (Google, GitHub, etc.)
  • Role-based access control: Student, Educator, Admin roles
  • Webhook sync: Automatic user sync to MongoDB
  • Session management: Secure JWT-based sessions
  • Profile management: User profile updates synced automatically

Environment Variables

Server Configuration

Add these to your server/.env file:
server/.env
CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...

Client Configuration

Add this to your client/.env file:
client/.env
VITE_CLERK_PUBLISHABLE_KEY=pk_test_...
Get your keys from the Clerk Dashboard. Create a new application if you haven’t already.

Setup Instructions

1

Create Clerk Application

  1. Go to Clerk Dashboard
  2. Click Add application
  3. Name your application (e.g., “SkillRise”)
  4. Choose authentication methods (Email, Google, GitHub, etc.)
  5. Click Create application
2

Configure API Keys

  1. From the Clerk Dashboard, navigate to API Keys
  2. Copy the Publishable Key and Secret Key
  3. Add them to your .env files (server and client)
3

Set Up Custom Metadata

Clerk stores user roles in session claims metadata. Configure this in your Clerk Dashboard:
  1. Go to SessionsCustomize session token
  2. Add this JSON to include role metadata:
{
  "metadata": "{{user.public_metadata}}"
}
This allows the server to access req.auth.sessionClaims.metadata.role.
4

Configure Webhooks

Clerk webhooks sync user data to your MongoDB database.
  1. Go to Webhooks in Clerk Dashboard
  2. Click Add Endpoint
  3. Enter your webhook URL:
    • Development: Use ngrokhttps://your-ngrok-url.ngrok.io/clerk
    • Production: https://your-domain.com/clerk
  4. Subscribe to these events:
    • user.created
    • user.updated
    • user.deleted
  5. Copy the Signing Secret and add it to server/.env as CLERK_WEBHOOK_SECRET

Role-Based Access Control

SkillRise implements three user roles:
RoleAccessAssigned By
studentBrowse courses, enroll, watch videos, use AI featuresDefault for new users
educatorCreate courses, view analytics, manage studentsAdmin approval
adminPlatform-wide management, approve educatorsManual assignment

Setting User Roles

Roles are stored in Clerk’s publicMetadata. You can set roles: Via Clerk Dashboard:
  1. Go to Users
  2. Select a user
  3. Scroll to MetadataPublic metadata
  4. Add:
    {
      "role": "educator"
    }
    
Programmatically (from your admin panel):
import { clerkClient } from '@clerk/express'

await clerkClient.users.updateUserMetadata(userId, {
  publicMetadata: { role: 'educator' }
})

Protecting Routes

SkillRise uses middleware to protect routes by role:
server/middlewares/authMiddleware.js
export const protectEducator = (req, res, next) => {
  if (!req.auth?.userId) {
    return res.status(401).json({ 
      success: false, 
      message: 'Unauthorized Access' 
    })
  }

  const role = req.auth.sessionClaims?.metadata?.role

  if (role !== 'educator') {
    return res.status(403).json({ 
      success: false, 
      message: 'Unauthorized Access' 
    })
  }

  next()
}

export const protectAdmin = (req, res, next) => {
  if (!req.auth?.userId) {
    return res.status(401).json({ 
      success: false, 
      message: 'Unauthorized Access' 
    })
  }

  const role = req.auth.sessionClaims?.metadata?.role

  if (role !== 'admin') {
    return res.status(403).json({ 
      success: false, 
      message: 'Unauthorized Access' 
    })
  }

  next()
}
Usage in routes:
server/routes/educatorRoutes.js
import { protectEducator } from '../middlewares/authMiddleware.js'

router.post('/courses', protectEducator, createCourse)
router.get('/dashboard', protectEducator, getDashboard)

Webhook Implementation

The Clerk webhook endpoint syncs user data to MongoDB:
server/controllers/webhooks.js
import { Webhook } from 'svix'
import User from '../models/User.js'

export const clerkWebhooks = async (req, res) => {
  try {
    const webhook = new Webhook(process.env.CLERK_WEBHOOK_SECRET)

    // Verify webhook signature
    await webhook.verify(JSON.stringify(req.body), {
      'svix-id': req.headers['svix-id'],
      'svix-timestamp': req.headers['svix-timestamp'],
      'svix-signature': req.headers['svix-signature'],
    })

    const { data, type } = req.body

    switch (type) {
      case 'user.created': {
        const userData = {
          _id: data.id,
          email: data.email_addresses[0].email_address,
          name: data.first_name + ' ' + data.last_name,
          imageUrl: data.image_url,
        }
        await User.create(userData)
        res.json({})
        break
      }

      case 'user.updated': {
        const userData = {
          email: data.email_addresses[0].email_address,
          name: data.first_name + ' ' + data.last_name,
          imageUrl: data.image_url,
        }
        await User.findByIdAndUpdate(data.id, userData)
        res.json({})
        break
      }

      case 'user.deleted': {
        await User.findByIdAndDelete(data.id)
        res.json({})
        break
      }

      default:
        res.json({})
    }
  } catch (error) {
    console.error(error)
    res.status(500).json({ 
      success: false, 
      message: 'An unexpected error occurred' 
    })
  }
}
Register the webhook route:
server/server.js
import { clerkWebhooks } from './controllers/webhooks.js'

app.post('/clerk', clerkWebhooks)
The webhook endpoint must be registered before express.json() middleware for Razorpay webhooks, but Clerk webhooks work with parsed JSON.

Server Middleware Setup

Initialize Clerk middleware in your Express app:
server/server.js
import { clerkMiddleware } from '@clerk/express'

// Apply Clerk middleware globally
app.use(clerkMiddleware())
This middleware:
  • Validates session tokens
  • Attaches req.auth with user info
  • Makes req.auth.userId and req.auth.sessionClaims available

Client Integration

Wrap Your App

client/src/main.jsx
import { ClerkProvider } from '@clerk/clerk-react'

const PUBLISHABLE_KEY = import.meta.env.VITE_CLERK_PUBLISHABLE_KEY

ReactDOM.createRoot(document.getElementById('root')).render(
  <ClerkProvider publishableKey={PUBLISHABLE_KEY}>
    <App />
  </ClerkProvider>
)

Access User Data

import { useUser } from '@clerk/clerk-react'

function Profile() {
  const { user, isLoaded } = useUser()

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

  return (
    <div>
      <h1>Welcome, {user.fullName}!</h1>
      <p>Email: {user.primaryEmailAddress.emailAddress}</p>
      <p>Role: {user.publicMetadata.role || 'student'}</p>
    </div>
  )
}

Protect Routes

import { SignedIn, SignedOut, RedirectToSignIn } from '@clerk/clerk-react'

function ProtectedPage() {
  return (
    <>
      <SignedIn>
        <h1>Protected Content</h1>
      </SignedIn>
      <SignedOut>
        <RedirectToSignIn />
      </SignedOut>
    </>
  )
}

Testing Webhooks Locally

1

Install ngrok

npm install -g ngrok
2

Start your server

cd server
npm run server
3

Expose localhost with ngrok

ngrok http 3000
Copy the HTTPS URL (e.g., https://abc123.ngrok.io)
4

Update Clerk webhook URL

In Clerk Dashboard → Webhooks → Edit your endpoint:
https://abc123.ngrok.io/clerk
5

Test webhook

Create a new user in Clerk Dashboard or sign up via your app. Check your server logs and MongoDB to verify the user was created.

Common Issues

  • Ensure CLERK_WEBHOOK_SECRET matches the signing secret in Clerk Dashboard
  • Verify you’re using the correct webhook endpoint URL
  • Check that headers svix-id, svix-timestamp, and svix-signature are being sent
  • Verify you’ve customized the session token in Clerk Dashboard (Sessions → Customize session token)
  • Ensure the role is set in publicMetadata, not privateMetadata
  • Check that clerkMiddleware() is applied before your routes
  • Check your webhook is subscribed to user.created, user.updated, user.deleted events
  • Verify your MongoDB connection is active
  • Check server logs for webhook errors

Resources

Clerk Documentation

Official Clerk documentation

Express SDK

Clerk Express SDK reference

React SDK

Clerk React SDK reference

Webhooks

Webhook integration guide

Build docs developers (and LLMs) love