clerk express sdk
Secure Express.js apps with Clerk authentication middleware
$ npx docs2skills add clerk-express-authClerk 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
| Function | Purpose |
|---|---|
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 integrationCLERK_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.