These integration guides are not official documentation and the Strapi Support Team will not provide assistance with them.
What Is Clerk?
Clerk is a developer-focused authentication platform that handles user management, session handling, and authentication flows for modern web applications. Rather than building authentication infrastructure from scratch, developers can use Clerk's prebuilt UI components, SDKs, and APIs to add secure sign-in functionality.
The platform supports multiple authentication methods out of the box: email verification codes, magic links, phone authentication via SMS, passkeys, social login through OAuth providers, and traditional username/password combinations. Clerk also provides enterprise features like Single Sign-On (SSO) and Multi-Factor Authentication (MFA) without requiring custom development.
For backend integrations, Clerk issues short-lived JWT session tokens that can be verified server-side. This token-based approach fits well with headless CMS architectures where authentication happens on the client while content APIs run separately.
Sign up for the Logbook, Strapi's Monthly newsletter
Why Integrate Clerk with Strapi
Combining Clerk's authentication capabilities with Strapi's content management features creates a powerful foundation for building secure, content-driven applications.
Here's what this integration enables:
- Offloaded authentication complexity: Clerk handles sign-in flows, password management, and session security entirely, letting Strapi focus on content management and API delivery
- Automatic user synchronization: When users sign up through Clerk, webhook events create corresponding records in Strapi's users-permissions system automatically
- Native RBAC compatibility: Strapi's role-based access control continues working normally—you assign roles to Clerk-synced users just like any other user
- Modern authentication methods: Social login, passwordless authentication, and MFA become available without custom development work
- Reduced security surface: Concentrating authentication in Clerk's managed infrastructure eliminates vulnerabilities associated with self-managed JWT systems
- Improved developer experience: Pre-built SDKs and UI components mean less code to write, test, and maintain
How to Integrate Clerk with Strapi
This integration uses the strapi-plugin-clerk-auth approach, which provides the most complete solution with built-in JWT verification using Clerk's secret key via the @clerk/backend SDK, user synchronization via webhooks with Svix signature verification, and route protection middleware including both JWT authentication and user ownership enforcement.
Prerequisites
Before starting, confirm your environment meets these requirements:
- Node.js version: Strapi v5 requires Node.js v20, v22, or v24 (LTS versions only). Clerk requires Node.js 18.17.0 or higher. Use Node.js v20+ to satisfy both requirements.
- Database: Strapi v5 supports MySQL 8.0+, MariaDB 10.5+, PostgreSQL 12.0+, or SQLite 3+.
- Accounts and API keys:
- A Clerk account with an application configured
- Clerk Publishable Key and Secret Key from your Clerk Dashboard
- Webhook signing secret (you'll create this during setup)
- Strapi v5 project: If you don't have one yet, create it with:
npx create-strapi@latest my-project --quickstartStep 1: Install the Clerk Auth Plugin
Navigate to your Strapi project's plugins directory and clone the authentication plugin:
cd src/plugins
git clone https://github.com/PaulBratslavsky/strapi-plugin-clerk-auth.git clerk-auth
cd clerk-auth
npm installThe plugin installs two critical dependencies:
@clerk/backendfor JWT verification using Clerk's SDKsvixfor webhook signature verification
Step 2: Register the Plugin
Add the plugin to your Strapi configuration. Create or update config/plugins.ts:
export default {
'clerk-auth': {
enabled: true,
resolve: './src/plugins/clerk-auth'
},
};This registers the Clerk authentication plugin in your Strapi v5 project, enabling JWT verification middleware and user synchronization functionality.
Step 3: Configure Environment Variables
Add your Clerk credentials to the .env file at your project root:
CLERK_SECRET_KEY=sk_test_your_secret_key_here
CLERK_WEBHOOK_SECRET=whsec_your_webhook_secret_hereYou can find your Secret Key in the Clerk Dashboard under API Keys. The webhook secret comes later when you configure the webhook endpoint.
Step 4: Configure the Webhook Endpoint
Webhooks keep Strapi's user database in sync with Clerk user events. When someone signs up, updates their profile, or deletes their account in Clerk, webhooks notify Strapi to create, update, or delete the corresponding user record in Strapi's users-permissions table. The webhook handler verifies signatures using the Svix library and stores the clerkId and user information from Clerk events.
The plugin exposes a webhook endpoint at /api/clerk-auth/webhook. Configure it in your Clerk Dashboard:
- Navigate to the Webhooks section
- Click "Add Endpoint"
- Enter your Strapi URL:
https://your-strapi-domain.com/api/clerk-auth/webhook - Subscribe to these events:
user.created,user.updated,user.deleted - Copy the Signing Secret and add it to your
.envfile asCLERK_WEBHOOK_SECRET
For local development, use a tunneling service like ngrok to expose your local Strapi instance:
ngrok http 1337Then use the ngrok URL as your webhook endpoint.
Step 5: Protect API Routes
The plugin provides two middleware functions for route protection: clerk-auth middleware for JWT verification and user authentication, and is-user-owner middleware for ownership-based access control.
- clerk-auth: Extracts and verifies the JWT token using Clerk's secret key, queries Strapi's users-permissions table for a matching clerkId, creates a new user record if none exists, and attaches the authenticated user to the request context.
- is-user-owner: Enforces ownership validation by comparing the authenticated user's ID against resource ownership to prevent unauthorized access to user-specific resources.
Apply these middlewares to routes that require authentication. Create or modify route files in src/api/[apiName]/routes/:
export default {
routes: [
{
method: 'GET',
path: '/users/me',
handler: 'user.me',
config: {
middlewares: ['plugin::clerk-auth.clerk-auth'],
auth: false,
},
},
{
method: 'PUT',
path: '/users/:id',
handler: 'user.update',
config: {
middlewares: [
'plugin::clerk-auth.clerk-auth',
'plugin::clerk-auth.is-user-owner'
],
auth: false,
},
},
],
};Setting auth: false is critical—it prevents conflicts between Clerk's JWT verification and Strapi's built-in authentication system.
Step 6: Send Authenticated Requests from Your Frontend
Your frontend application needs to obtain tokens from Clerk and include them in API requests to Strapi. Here's how that looks:
// Get the session token from Clerk
const token = await clerk.session.getToken();
// Include it in requests to your Strapi API
const response = await fetch('https://your-strapi-api.com/api/users/me', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
});
const userData = await response.json();The Clerk SDKs for React, Next.js, and other frameworks provide hooks and components that simplify token retrieval and authentication integration with Strapi.
Step 7: Verify the Integration
Rebuild and restart your Strapi server:
npm run build
npm run developTest the integration by:
- Signing in through your Clerk-powered frontend
- Making an authenticated request to a protected Strapi endpoint
- Checking the Strapi Admin Panel to confirm the user was created
Project Example: Building a Membership Content Platform
A membership content platform demonstrates how Clerk and Strapi work together in practice. Users authenticate through Clerk to access premium articles, courses, or resources managed in Strapi.
Architecture Overview
The system has three main components:
- Frontend application: Handles user authentication through Clerk's UI components
- Clerk: Manages authentication, issues JWTs, and sends webhook events
- Strapi: Stores content, manages user records, and enforces access control through RBAC
When a user signs up, Clerk's webhook notifies Strapi to create a user record. The frontend then requests protected content by including the Clerk JWT in API calls. Strapi's middleware verifies the token and checks permissions before returning content.
Content Model Setup
Create a content type for membership content in Strapi. Using the Content-Type Builder, define an Article type with these fields:
title(Text)content(Rich Text)tier(Enumeration: free, basic, premium)author(Relation to User)
Protected Route Implementation
Create a custom controller that filters content based on user membership tier:
// src/api/article/controllers/article.ts
export default {
async findAccessible(ctx) {
const user = ctx.state.user;
if (!user) {
// Return only free content for unauthenticated users
return await strapi.documents('api::article.article').findMany({
filters: { tier: 'free' },
populate: ['author'],
});
}
// Get user's membership tier from metadata
const userTier = user.membershipTier || 'free';
// Define accessible tiers based on membership level
const accessibleTiers = {
free: ['free'],
basic: ['free', 'basic'],
premium: ['free', 'basic', 'premium'],
};
return await strapi.documents('api::article.article').findMany({
filters: {
tier: { $in: accessibleTiers[userTier] }
},
populate: ['author'],
});
},
};Route Configuration
Register the protected route with Clerk authentication middleware:
// src/api/article/routes/custom.ts
// Protected route for accessing public articles without Clerk authentication
export default {
routes: [
{
method: 'GET',
path: '/articles/accessible',
handler: 'article.findAccessible',
config: {
// Apply Clerk JWT verification middleware from the strapi-plugin-clerk-auth
middlewares: ['plugin::clerk-auth.clerk-auth'],
// Disable Strapi's built-in authentication to prevent JWT validation conflicts
// Clerk's custom middleware handles token verification instead
auth: false,
},
},
],
};User Tier Synchronization
Extend the webhook handler to sync membership tier data from Clerk's user metadata:
// In your webhook handler
case 'user.updated':
const existingUser = await strapi.db
.query('plugin::users-permissions.user')
.findOne({ where: { clerkId: evt.data.id } });
if (existingUser) {
await strapi.db.query('plugin::users-permissions.user').update({
where: { id: existingUser.id },
data: {
clerkId: evt.data.id,
fullName: `${evt.data.first_name} ${evt.data.last_name}`,
// Optional: map custom metadata from Clerk public_metadata
...(evt.data.public_metadata?.tier && { membershipTier: evt.data.public_metadata.tier }),
},
});
}
break;Frontend Integration
On the frontend, fetch accessible content using the authenticated user's token:
import { useAuth } from '@clerk/clerk-react';
function ArticleList() {
const { getToken } = useAuth();
const [articles, setArticles] = useState([]);
useEffect(() => {
async function fetchArticles() {
const token = await getToken();
const response = await fetch(
`${process.env.STRAPI_URL}/api/articles/accessible`,
{
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
},
}
);
const data = await response.json();
setArticles(data);
}
fetchArticles();
}, [getToken]);
return (
<div>
{articles.map((article) => (
<article key={article.id}>
<h2>{article.title}</h2>
<span>{article.tier}</span>
</article>
))}
</div>
);
}This pattern scales to other content-driven applications: e-commerce platforms with protected checkout, blogs with subscriber-only content, or SaaS applications with tiered feature access.
Strapi Open Office Hours
If you have any questions about Strapi 5 or just would like to stop by and say hi, you can join us at Strapi's Discord Open Office Hours, Monday through Friday, from 12:30 pm to 1:30 pm CST: Strapi Discord Open Office Hours.
For more details, visit the Strapi documentation and Clerk documentation.
Get Started in Minutes
npx create-strapi-app@latest in your terminal and follow our Quick Start Guide to build your first Strapi project.