Skip to main content
Docs

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

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-After header 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:

  • 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.

Note

If you're looking for a real-world example, have a look at clerk-sdk-go.

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.

Note

The code blocks below will be written in pseudo-code. If you're looking for real-world examples, have a look at @clerk/fastify and @clerk/express.

Create a Clerk client

Use from @clerk/backend to create your default Clerk client which will be used for the middleware.

client.ts
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.

clerk-middleware.ts
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.

get-auth.ts
export const getAuth = (context) => context.get('clerkAuth')

Your end-users can use this utility in cases like these:

index.ts
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 reference to see how it's implemented.

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.

require-auth.ts
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:

index.ts
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:

index.ts
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

What did you think of this content?

Last updated on