Clerk Express SDK logo

clerk express sdk

Secure Express.js apps with Clerk authentication middleware

$ npx docs2skills add clerk-express-auth
SKILL.md

Clerk Express SDK

Secure Express.js apps with Clerk authentication middleware

What this skill does

Clerk Express SDK provides backend authentication utilities for Express.js applications, enabling secure user session management without building custom auth from scratch. It validates JWTs from frontend Clerk SDKs, attaches user authentication data to Express request objects, and provides middleware to protect routes based on authentication status.

The SDK acts as the backend counterpart to Clerk's frontend authentication SDKs. When users authenticate through a Clerk-powered frontend (React, Next.js, etc.), their session tokens are validated by your Express backend using this middleware. This enables full-stack authentication with minimal backend code while maintaining security best practices for session handling and user data access.

Unlike standalone Express auth solutions, Clerk handles the complex authentication flows (OAuth, passwordless, MFA) on the frontend, while this SDK focuses purely on session validation and user data retrieval on the backend. It integrates seamlessly with Clerk's user management system, providing access to user profiles, organizations, and custom claims through a unified API.

Prerequisites

  • Node.js 18+
  • Express.js application
  • Active Clerk application with API keys
  • Frontend using a Clerk SDK (React, Next.js, etc.) for user authentication
  • Environment variable management (dotenv recommended)

Quick start

npm install @clerk/express dotenv
// index.js
import 'dotenv/config'
import express from 'express'
import { clerkMiddleware, requireAuth, getAuth, clerkClient } from '@clerk/express'

const app = express()

// Add Clerk middleware to all routes
app.use(clerkMiddleware())

// Public route
app.get('/', (req, res) => {
  res.json({ message: 'Public endpoint' })
})

// Protected route
app.get('/protected', requireAuth(), async (req, res) => {
  const { userId } = getAuth(req)
  const user = await clerkClient.users.getUser(userId)
  res.json({ user })
})

app.listen(3000, () => console.log('Server running on port 3000'))
# .env
CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...

Core concepts

Middleware Chain: Clerk Express uses a two-layer middleware approach. clerkMiddleware() processes all requests to extract and validate session tokens, attaching authentication data to req.auth. requireAuth() builds on this by enforcing authentication requirements and handling redirects for protected routes.

Session Validation: The SDK validates JWTs sent from frontend clients without requiring database calls. Session tokens contain encrypted user and session data that Clerk's backend validates using your secret key. This enables stateless authentication while maintaining security.

Auth Object: Every request processed by clerkMiddleware() gets an auth object containing userId, sessionId, orgId, and other authentication metadata. This object is null for unauthenticated requests and populated for valid sessions.

Backend API Integration: The SDK includes clerkClient for server-side operations like fetching user details, managing organizations, or performing admin actions. This client uses your secret key for privileged operations that frontend clients cannot perform.

Key API surface

FunctionPurpose
clerkMiddleware()Core middleware that validates sessions and attaches auth data
requireAuth()Protects routes, redirects unauthenticated users
getAuth(req)Extracts auth object from request
clerkClient.users.getUser(userId)Fetch complete user profile
clerkClient.organizations.getOrganization(orgId)Retrieve organization data
clerkClient.sessions.getSession(sessionId)Get session details
clerkClient.users.updateUser(userId, params)Update user profile
clerkClient.organizations.createOrganization(params)Create new organization

Common patterns

Basic route protection:

app.get('/dashboard', requireAuth(), (req, res) => {
  const { userId } = getAuth(req)
  res.json({ message: `Welcome user ${userId}` })
})

Conditional authentication:

app.get('/profile', clerkMiddleware(), async (req, res) => {
  const { userId } = getAuth(req)
  if (!userId) {
    return res.status(401).json({ error: 'Authentication required' })
  }
  const user = await clerkClient.users.getUser(userId)
  res.json({ user })
})

Organization-based access:

app.get('/admin', requireAuth(), async (req, res) => {
  const { userId, orgId } = getAuth(req)
  if (!orgId) {
    return res.status(403).json({ error: 'Organization membership required' })
  }
  
  const membership = await clerkClient.organizations.getOrganizationMembership({
    organizationId: orgId,
    userId
  })
  
  if (membership.role !== 'admin') {
    return res.status(403).json({ error: 'Admin access required' })
  }
  
  res.json({ message: 'Admin panel access granted' })
})

Custom redirect handling:

app.use('/api', requireAuth({
  unauthorizedUrl: '/login',
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY
}))

Configuration

// Custom middleware options
app.use(clerkMiddleware({
  publishableKey: process.env.CLERK_PUBLISHABLE_KEY, // Override default
  secretKey: process.env.CLERK_SECRET_KEY,           // Override default
  apiUrl: 'https://api.clerk.dev',                   // Custom API endpoint
  apiVersion: 'v1',                                  // API version
  jwtKey: process.env.CLERK_JWT_KEY                  // Custom JWT key
}))

// requireAuth options
app.use('/protected', requireAuth({
  unauthorizedUrl: '/signin',      // Redirect destination
  returnBackUrl: '/dashboard'      // Post-auth redirect
}))

Environment variables:

  • CLERK_PUBLISHABLE_KEY: Client-side key for frontend integration
  • CLERK_SECRET_KEY: Server-side key for API access (required)
  • CLERK_JWT_KEY: Custom JWT verification key (optional)

Best practices

Always use clerkMiddleware first: Apply clerkMiddleware() before any routes that need auth data. It should be one of the first middleware in your chain.

Separate auth levels: Use clerkMiddleware() for optional auth and requireAuth() for mandatory auth rather than mixing approaches in the same route.

Cache user data appropriately: Avoid fetching user data on every request. Cache user profiles in memory or Redis for frequently accessed routes.

const userCache = new Map()

app.get('/profile', requireAuth(), async (req, res) => {
  const { userId } = getAuth(req)
  
  if (!userCache.has(userId)) {
    const user = await clerkClient.users.getUser(userId)
    userCache.set(userId, user)
  }
  
  res.json({ user: userCache.get(userId) })
})

Handle async errors properly: Wrap Clerk API calls in try-catch blocks since they make network requests that can fail.

Use TypeScript globals: Add the global type reference to enable IntelliSense for the auth object:

// types/globals.d.ts
/// <reference types="@clerk/express/env" />

Gotchas and common mistakes

clerkMiddleware must run first: If you place other middleware before clerkMiddleware(), the auth object won't be available. Always apply it early in your middleware chain.

requireAuth redirects by default: requireAuth() sends redirects for unauthenticated users, which breaks API endpoints. Use clerkMiddleware() + manual auth checks for JSON APIs.

Environment variables are required: The SDK throws runtime errors if CLERK_SECRET_KEY is missing. Always validate env vars are loaded before starting your server.

getAuth returns null for invalid sessions: Always check if getAuth(req) returns null or if userId exists before using auth data.

Session tokens expire: JWTs have expiration times. Handle cases where previously valid tokens become invalid during long-running requests.

Organization context is optional: orgId in the auth object is only present if the user is in an organization context on the frontend. Don't assume it exists.

clerkClient methods are async: All clerkClient methods return promises. Forgetting await leads to Promise objects instead of data.

CORS issues with redirects: When requireAuth() redirects, browsers may block cross-origin redirects. Configure CORS properly or use JSON error responses for API routes.

Secret key vs publishable key confusion: Use secret key for backend SDK initialization, publishable key for frontend. Don't expose secret keys to clients.

Middleware order with error handlers: Place Clerk middleware before your error handling middleware, or authentication errors won't be caught properly.