Skip to main content
Docs

Verify API keys in your Next.js application with Clerk

When building a server that needs to accept and verify API keys issued by Clerk, it's crucial to verify these keys on your backend to ensure the request is coming from an authenticated client.

Clerk's Next.js SDK provides a built-in auth() function that supports token validation via the acceptsToken parameter. This lets you specify which type(s) of token your API route should accept. You can also use the auth.protect() method to check if a request includes a valid machine token (e.g. API key or OAuth token) and enforce access rules accordingly.

By default, acceptsToken is set to session_token, which means API keys will not be accepted unless explicitly configured. You can pass either a single token type or an array of token types to acceptsToken. To learn more about the supported token types, see the auth() parameters documentation.

Below are two examples of verifying API keys in a Next.js API route using Clerk's SDK:

Example 1: Accepting a single token type

In the following example, the acceptsToken parameter is set to only accept api_keys.

  • If the API key is invalid or missing, auth() will return null for userId and other properties, and the request will be rejected with a 401 response.
  • If the API key is valid, userId and claims are returned.
app/api/example/route.ts
import { NextResponse } from 'next/server'
import { auth } from '@clerk/nextjs/server'

export async function GET() {
  const { isAuthenticated, claims, userId } = await auth({ acceptsToken: 'api_key' })

  if (!isAuthenticated) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  return NextResponse.json({ userId, claims })
}

Example 2: Accepting multiple token types

In the following example, the acceptsToken option allows session_tokens, oauth_tokens, and api_keys.

  • If the token is invalid or missing, auth() will return false for isAuthenticated and null for other properties, like userId.
  • If the token is an api_key, the code can access the associated user's data using the userId.
  • If the token is valid, isAuthenticated is true and userId is returned and available for use in the application logic. This example includes pseudo-code that uses the userId to get data from a database.
app/api/example/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { auth } from '@clerk/nextjs/server'

export async function POST(req: NextRequest) {
  // Accept session_token, oauth_token, and api_key types
  const { isAuthenticated, tokenType, userId, scopes } = await auth({
    acceptsToken: ['session_token', 'oauth_token', 'api_key'],
  })

  // If the token is invalid or missing
  if (!isAuthenticated) {
    return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
  }

  // Check if the token is an api key and if it doesn't have the required scope
  if (tokenType === 'api_key' && !scopes?.includes('write:users')) {
    return NextResponse.json({ error: 'API Key missing the "write:users" scope' }, { status: 401 })
  }

  // If the token is valid, move forward with the application logic
  // This example includes pseudo-code for getting data from a database using the userId
  const data = db.select().from(user).where(eq(user.id, userId))

  return NextResponse.json({ data })
}

You can also protect entire route groups using clerkMiddleware(). See how to implement this in the middleware docs.

Feedback

What did you think of this content?

Last updated on