ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β π shadcn/directory/clerk/clerk-docs/guides/development/sdk-development/backend-only β ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
When creating a backend-only SDK, you have two options for implementing the BAPI endpoints: either develop a backend SDK that encompasses all BAPI endpoints or create an SDK tailored for an existing backend framework.
The source of truth for all BAPI endpoints is the BAPI reference docs{{ target: '_blank' }}. For Node.js backend frameworks, you can build on top of the JS Backend SDK.
[!IMPORTANT] BAPI has rate limits to help protect users against brute-force attacks or stop abuse of Clerk's platform. Be sure to include a backoff mechanism into your fetching logic and respect the
Retry-Afterheader to gracefully handle any active rate limits.
If you're using @clerk/backend to build an SDK for an existing framework, these additional features are expected:
@clerk/backend optionsrequireAuth helper)You can manually create a wrapper library around the BAPI OpenAPI{{ target: '_blank' }} or use one the many automatic SDK generation tools that take in OpenAPI definitions.
[!NOTE] If you're looking for a real-world example, have a look at
clerk-sdk-go.
@clerk/backend is built for Node.js/V8 isolates (Cloudflare Workers, Vercel Edge Runtime, etc.). Itβs the foundational package for all JavaScript Backend SDKs and works across all JavaScript runtimes. By using @clerk/backend you can be sure to communicate with Clerkβs BAPI in a correct and secure way.
<Steps> ### Create a Clerk client[!NOTE] The code blocks below will be written in pseudo-code. If you're looking for real-world examples, have a look at
@clerk/fastifyand@clerk/express.
Use createClerkClient from @clerk/backend to create your default Clerk client which will be used for the middleware.
import { createClerkClient } from '@clerk/backend'
const API_VERSION = process.env.CLERK_API_VERSION || 'v1'
const SECRET_KEY = process.env.CLERK_SECRET_KEY || ''
const PUBLISHABLE_KEY = process.env.CLERK_PUBLISHABLE_KEY || ''
const API_URL = process.env.CLERK_API_URL || ''
const JWT_KEY = process.env.CLERK_JWT_KEY || ''
const SDK_METADATA = {
name: PACKAGE_NAME,
version: PACKAGE_VERSION,
environment: process.env.NODE_ENV,
}
export const clerkClient = createClerkClient({
secretKey: SECRET_KEY,
apiUrl: API_URL,
apiVersion: API_VERSION,
jwtKey: JWT_KEY,
userAgent: `${PACKAGE_NAME}@${PACKAGE_VERSION}`,
sdkMetadata: SDK_METADATA,
})
Inside the middleware, youβll use the user-provided Clerk client (or use the default one created in the previous step) and authenticate the request. authenticateRequest returns Promise<RequestState>. The middleware should set requestState.toAuth() into its context as this will contain the resolved signed-in/signed-out Auth object. This way other helpers can access it later in the chain.
import { clerkClient as defaultClerkClient } from './client.ts'
const clerkMiddleware = (options) => {
return async (context, next) => {
const clerkClient = options.clerkClient || defaultClerkClient
const requestState = await clerkClient.authenticateRequest(context.req, {
authorizedParties: ['https://example.com'],
})
context.set('clerkAuth', requestState.toAuth())
context.set('clerk', clerkClient)
await next()
}
}
getAuth helperThis utility will access the stored requestState (in the example above saved as clerkAuth) and return it.
export const getAuth = (context) => context.get('clerkAuth')
Your end-users can use this utility in cases like these:
const app = new Framework()
app.use('*', clerkMiddleware())
app.get('/', (context) => {
const auth = getAuth(context)
if (!auth?.userId) {
return context.json({ message: 'Not logged in' })
}
return context.json({ message: 'Logged in', userId: auth.userId })
})
[!TIP] See the Next.js
getAuth()reference to see how it's implemented.
requireAuth helperThis utility will require auth requests for user authenticated or authorized requests. An HTTP 401 status code is returned for unauthorized requests.
export const requireAuth = (context, next) => {
if (!hasAuthObject(context)) {
throw new Error('Middleware required')
}
if (!getAuth(context).userId) {
context.status = 401
return
}
return next()
}
Your end-users can use this utility in cases like these:
const app = new Framework()
app.get('/path', requireAuth())
Your end-users will also have access to a has() function on the Auth object. They can combine it with requireAuth() like so:
const app = new Framework()
const hasPermission = (context, next) => {
const auth = getAuth(context)
if (!auth.has({ permission: 'org:feature:permission' })) {
context.status = 403
return
}
return next()
}
app.get('/path', requireAuth(), hasPermission())
</Steps>β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ