npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

strapi-plugin-clerk-auth

v0.0.1

Published

Support Clerk Auth and Strapi U&P.

Readme

strapi-plugin-clerk-auth

A Strapi v5 plugin that replaces Strapi's built-in JWT authentication with Clerk. Clerk handles sign-in/sign-up on the client; this plugin verifies Clerk JWTs on the Strapi side, auto-creates users in the users-permissions table, and protects user routes with ownership checks.

How authentication works

In a traditional Strapi setup, Strapi is both the auth provider and the backend — it issues JWTs via /api/auth/local and verifies them on subsequent requests. This plugin changes that:

  • Clerk is the auth provider. Sign-in/sign-up happens entirely between the client app and Clerk's servers. Clerk issues the JWT. Strapi is never involved in the sign-in flow.
  • Strapi is the backend only. It receives the Clerk JWT in the Authorization header, verifies it using the Clerk secret key, and maps it to a users-permissions user.
  • Strapi's built-in JWT auth is disabled on protected routes (config.auth = false). Without this, Strapi would reject the Clerk JWT before the plugin middleware runs.

This means:

  • A user can be signed in (valid Clerk session) even if Strapi is down. The client app should handle API errors gracefully (e.g. show a retry screen).
  • Strapi roles and permissions still work. The middleware sets ctx.state.user to a real users-permissions user, so everything downstream (roles, policies, content ownership) behaves normally.
  • No Strapi JWT is ever issued. The Clerk JWT is the only token in the system.
sequenceDiagram
    participant App as Mobile / Web App
    participant Clerk as Clerk API
    participant Strapi as Strapi API
    participant DB as Strapi DB

    Note over App,Clerk: 1. Authentication (Clerk — independent of Strapi)
    App->>Clerk: Sign in / Sign up
    Clerk-->>App: Session + JWT
    Note over App: User is now "signed in"<br/>Strapi is not involved yet

    Note over App,DB: 2. First API call (e.g. GET /api/users/me)
    App->>Strapi: Request with Bearer <Clerk JWT>

    rect rgb(240, 248, 255)
        Note over Strapi: clerk-auth middleware
        Strapi->>Clerk: Verify JWT (using clerkSecretKey)
        Clerk-->>Strapi: JWT payload (sub, email, name)
        Strapi->>DB: Find user by clerkId
        alt User not found
            Strapi->>DB: Create user (clerkId, email, role=authenticated)
            DB-->>Strapi: New user
        end
        Note over Strapi: Set ctx.state.user
    end

    rect rgb(255, 248, 240)
        Note over Strapi: is-user-owner middleware
        Strapi->>Strapi: Check user owns requested resource
    end

    Strapi-->>App: User data

Architecture

graph TD
    subgraph Plugin Lifecycle
        R[register] -->|1| EXTEND[Extend User model<br/>+ clerkId, fullName]
        R -->|2| INJECT[Inject middlewares<br/>on users-permissions routes]
        R -->|3| AUTHOFF[Disable Strapi built-in auth<br/>on protected routes]
        B[bootstrap] -->|4| VALIDATE[Validate clerkId field exists]
        B -->|5| LIFECYCLE[Register profile lifecycle hooks<br/>if userProfileContentType configured]
    end

    subgraph Request Pipeline
        REQ[Incoming Request] --> MW1[clerk-auth middleware]
        MW1 -->|verify JWT, find/create user| MW2[is-user-owner middleware]
        MW2 -->|check ownership| CTRL[Controller]
    end

    subgraph Webhook Pipeline
        WH[POST /clerk-auth/webhook] --> VERIFY[Verify Svix signature]
        VERIFY --> SYNC[Create / Update / Delete user]
    end

Protected routes

The plugin takes over authentication for these users-permissions routes:

| Route | Handler | Middlewares | |---|---|---| | GET /api/users | user.find | clerk-auth | | GET /api/users/:id | user.findOne | clerk-auth + is-user-owner | | PUT /api/users/:id | user.update | clerk-auth + is-user-owner | | DELETE /api/users/:id | user.destroy | clerk-auth + is-user-owner | | GET /api/users/me | user.me | clerk-auth |

Strapi's built-in auth (config.auth) is disabled on these routes so the Clerk JWT isn't rejected before the middleware runs.

Setup guide

1. Install dependencies

The plugin requires @clerk/backend and svix (for webhook verification):

cd plugin/strapi-plugin-clerk-auth
npm install
npm run build

2. Register the plugin in your Strapi project

In your Strapi project's config/plugins.ts:

import path from "node:path";

export default () => ({
  "clerk-auth": {
    enabled: true,
    resolve: path.resolve(process.cwd(), "../plugin/strapi-plugin-clerk-auth"),
    config: {
      clerkSecretKey: process.env.CLERK_SECRET_KEY,
      clerkWebhookSecret: process.env.CLERK_WEBHOOK_SECRET, // optional
      userProfileContentType: "api::user-profile.user-profile", // optional
    },
  },
});

3. Set environment variables

Add to your Strapi project's .env:

CLERK_SECRET_KEY=sk_test_...
CLERK_WEBHOOK_SECRET=whsec_...  # optional, from Clerk Dashboard > Webhooks

| Variable | Required | Description | |---|---|---| | CLERK_SECRET_KEY | Yes | Clerk secret key for JWT verification. Found in Clerk Dashboard > API Keys. | | CLERK_WEBHOOK_SECRET | No | Svix signing secret for webhook verification. Required only if using webhooks. |

4. Build the plugin

cd plugin/strapi-plugin-clerk-auth
npm run build

Then restart Strapi. On startup you should see:

[clerk-auth] Added clerkId and fullName fields to User model
[clerk-auth] Middlewares registered on users-permissions routes

5. Configure the client

Your client app sends Clerk JWTs as Bearer tokens. Example with a React Native / Expo app:

const { getToken } = useAuth(); // from @clerk/clerk-expo

const token = await getToken();
const res = await fetch("http://localhost:1337/api/users/me", {
  headers: { Authorization: `Bearer ${token}` },
});
const user = await res.json();

On the first request, the plugin auto-creates a Strapi user with the authenticated role.

Plugin config reference

| Option | Type | Default | Description | |---|---|---|---| | clerkSecretKey | string | "" | (Required) Clerk secret key for verifying JWTs. | | clerkWebhookSecret | string | "" | Svix webhook signing secret. If empty, webhook signature verification is skipped. | | userProfileContentType | string | "" | UID of a content type to auto-create as a profile when a user is created (e.g. api::user-profile.user-profile). The content type must have a user relation and a name text field. |

Webhooks

The plugin exposes POST /api/clerk-auth/webhook (no auth required) to receive Clerk webhook events:

| Event | Action | |---|---| | user.created | Creates a Strapi user if one doesn't exist for the Clerk ID | | user.updated | Updates email, username, and fullName | | user.deleted | Deletes the Strapi user matching the Clerk ID |

To set up webhooks:

  1. Go to Clerk Dashboard > Webhooks
  2. Add endpoint: https://your-strapi-domain.com/api/clerk-auth/webhook
  3. Subscribe to user.created, user.updated, user.deleted
  4. Copy the signing secret to CLERK_WEBHOOK_SECRET

User model extensions

The plugin adds two fields to plugin::users-permissions.user at registration time (no migration needed):

| Field | Type | Description | |---|---|---| | clerkId | string (unique) | The Clerk user ID (user_xxx) | | fullName | string | Full name derived from Clerk's first_name + last_name |

Profile auto-creation

When userProfileContentType is configured, the plugin subscribes to database lifecycle hooks:

  • afterCreate: Creates a profile document linked to the new user. If no fullName is available, generates a random username (e.g. "SwiftTiger4821").
  • beforeDelete: Deletes the associated profile when a user is deleted.

Development

# Watch mode (rebuilds on file changes)
npm run watch

# Type check
npm run test:ts:back

License

MIT