Skip to main content
Docs

Integrate Encore with Clerk

Encore is an open source backend framework for TypeScript with built-in infrastructure automation and observability.

This tutorial outlines how to integrate Clerk's authentication into your Encore backend application, allowing you to manage user authentication and sessions securely. This guide uses TypeScript and Encore's built-in auth handler pattern.

Install @clerk/backend

The Clerk JS Backend SDK provides utilities to verify session tokens and manage users.

Run the following command to install the SDK:

terminal
npm install @clerk/backend
terminal
pnpm add @clerk/backend
terminal
yarn add @clerk/backend
terminal
bun add @clerk/backend

Set your Clerk API keys

  1. In the Clerk Dashboard, navigate to the API keys page.

  2. In the Quick Copy section, copy your Clerk .

  3. Encore provides built-in secrets management to securely store sensitive values. Add your key to Encore's secrets with the following command:

    terminal
    encore secret set --dev ClerkSecretKey

Tip

When you deploy your Encore app, you can set a different value for production to keep your keys secure.

terminal
encore secret set --prod ClerkSecretKey

Create a Clerk client

  1. Create an auth folder.
  2. In the auth folder, create a clerk.ts file.
  3. In your clerk.ts file, configure the Clerk client using the Clerk you set, using createClerkClientClerk Icon from @clerk/backend:
auth/clerk.ts
import { createClerkClient } from '@clerk/backend'
import { secret } from 'encore.dev/config'

const clerkSecretKey = secret('ClerkSecretKey')

export const clerk = createClerkClient({
  secretKey: clerkSecretKey(),
})

Create an auth handler

Encore has a built-in auth handler pattern for protecting endpoints. In the auth folder, create a handler.ts file and copy and paste the following code. This is the auth handler that verifies Clerk session tokens using the verifyToken() function and makes authenticated user data available to your endpoints:

Warning

For optimal performance and to avoid rate limiting, it's recommended to use the useUser() hook on the client-side when possible. Only use getUser() when you specifically need user data in a server context.

auth/handler.ts
import { APIError, Gateway, Header } from 'encore.dev/api'
import { authHandler } from 'encore.dev/auth'
import { verifyToken } from '@clerk/backend'
import { secret } from 'encore.dev/config'
import { clerk } from './clerk'

const clerkSecretKey = secret('ClerkSecretKey')

interface AuthParams {
  authorization: Header<'Authorization'>
}

export interface AuthData {
  userID: string
  email: string
}

export const auth = authHandler<AuthParams, AuthData>(async (params) => {
  const token = params.authorization?.replace('Bearer ', '')

  if (!token) {
    throw APIError.unauthenticated('Missing session token')
  }

  try {
    const payload = await verifyToken(token, {
      secretKey: clerkSecretKey(),
      // Optional: restrict allowed origins to prevent subdomain cookie leakage
      // Replace with your authorized parties
      authorizedParties: ['http://localhost:3001', 'https://example.com'],
    })

    const userId = payload.sub

    if (!userId) {
      throw new Error('No user ID in session token')
    }

    const user = await clerk.users.getUser(userId)

    return {
      userID: user.id,
      email:
        user.emailAddresses.find((e) => e.id === user.primaryEmailAddressId)?.emailAddress || '',
    }
  } catch (err) {
    throw APIError.unauthenticated('Invalid session token')
  }
})

export const gateway = new Gateway({
  authHandler: auth,
})

Protect your routes using getAuthData()

This endpoint gets the authenticated user's profile information. The auth: true option protects the endpoint, allowing only authenticated requests to access it. The getAuthData() function retrieves the authenticated user's data from the auth handler. Learn more about authentication in Encore documentation.

user/profile.ts
import { api } from 'encore.dev/api'
import { getAuthData } from '~encore/auth'

export const getProfile = api(
  { expose: true, auth: true, method: 'GET', path: '/user/profile' },
  async () => {
    const auth = getAuthData()!

    return {
      userId: auth.userID,
      email: auth.email,
    }
  },
)

Run your project

Start your Encore app with the following command:

encore run

Open the local development dashboard at http://localhost:9400 in your browser to view traces for all requests, including auth handler execution.

If you want to add more to your Clerk + Encore integration, such as database integration, webhooks, or organizations, check out this advanced Clerk + Encore tutorial guide.

Feedback

What did you think of this content?

Last updated on