Backend-only SDK
When creating a backend-only SDK, you have two options for implementing the endpoints: either or .
The source of truth for all BAPI endpoints is the BAPI reference docs. For Node.js backend frameworks, you can build on top of the .
Expected features
- User only needs to provide their
- Centralized request authentication (e.g. in a middleware or plugin)
- Give access to the instance of BAPI client (so that users can use all methods)
- User should be able to limit access to routes by checking for roles and permissions
If you're using @clerk/backend
to build an SDK for an existing framework, these additional features are expected:
- User should be able to use all
@clerk/backend
options
Optional features
- User should be able to enforce authentication on individual routes (e.g. with a helper)
- Use singleton pattern to only create a pre-configured instance of Clerk backend client
Implementation: BAPI
You can manually create a wrapper library around the BAPI OpenAPI or use one the many automatic SDK generation tools that take in OpenAPI definitions.
Implementation: Node.js backend framework
@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.
Create a Clerk client
Use 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,
})
Create your middleware/plugin
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. returns Promise<RequestState>
. The middleware should set requestState.toAuth()
into its context as this will contain the resolved signed-in/signed-out 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()
}
}
Create a getAuth
helper
This 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 })
})
Create a requireAuth
helper
This 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 function on the 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: 'permission' })) {
context.status = 403
return
}
return next()
}
app.get('/path', requireAuth(), hasPermission())
Feedback
Last updated on