Overview
Clerk is a modern authentication and user management platform that uses RS256 signing with JWKS for token verification. This guide shows you how to integrate Clerk authentication with Revstack.
Prerequisites
A Clerk account and application
Clerk session tokens being issued to your frontend
Your Clerk issuer URL (also known as Frontend API URL)
Installation
npm install @revstackhq/auth
Configuration
1. Get your Clerk issuer URL
From your Clerk dashboard:
Go to API Keys
Copy your Issuer URL (e.g., https://clerk.your-domain.com or https://your-app.clerk.accounts.dev)
The issuer URL is the same as your Frontend API URL in Clerk’s dashboard.
2. Build the auth contract
Create an auth contract using your Clerk configuration:
import { buildAuthContract } from "@revstackhq/auth" ;
const authContract = buildAuthContract ( "clerk" , {
issuerUrl: "https://clerk.your-domain.com" ,
});
The contract builder automatically:
Sets the JWKS URI to https://clerk.your-domain.com/.well-known/jwks.json
Sets the issuer to match your issuer URL
Configures RS256 verification strategy
Verification
Initialize the verifier
import { RevstackAuth } from "@revstackhq/auth" ;
const auth = new RevstackAuth ( authContract );
Verify tokens
In your API routes or middleware:
export async function authMiddleware ( req , res , next ) {
const session = await auth . validate ( req . headers . authorization );
if ( ! session . isValid ) {
return res . status ( 401 ). json ({ error: session . error });
}
// Attach user info to request
req . userId = session . userId ; // Clerk user ID (e.g., "user_2xM...")
req . claims = session . claims ; // Full JWT payload
next ();
}
Token structure
Clerk session tokens contain standard OIDC claims plus Clerk-specific metadata:
{
"iss" : "https://clerk.your-domain.com" ,
"sub" : "user_2xMPL1f2Q3Z4z5R6W7S8T9" ,
"azp" : "https://your-app.com" ,
"iat" : 1516239022 ,
"exp" : 1516242622 ,
"sid" : "sess_2xMPL1f2Q3Z4z5R6W7S8T9" ,
"organization_id" : "org_2xMPL1f2Q3Z4z5R6W7S8T9"
}
Clerk includes additional metadata in the JWT that you can access via typed claims:
interface ClerkClaims {
sid : string ; // Session ID
organization_id ?: string ; // Organization ID (if using Clerk Organizations)
metadata ?: Record < string , any >; // Public metadata
}
const session = await auth . validate < ClerkClaims >( token );
if ( session . isValid ) {
console . log ( session . userId ); // "user_2xM..."
console . log ( session . claims . sid ); // "sess_2xM..."
console . log ( session . claims . organization_id ); // "org_2xM..."
}
Custom user ID claim
By default, Revstack uses the sub claim (Clerk user ID). If you need to use a different claim:
const authContract = buildAuthContract ( "clerk" , {
issuerUrl: "https://clerk.your-domain.com" ,
userIdClaim: "organization_id" , // Use org ID instead of user ID
});
Complete example
Here’s a full Next.js API route integration:
// pages/api/profile.ts
import { NextApiRequest , NextApiResponse } from "next" ;
import { buildAuthContract , RevstackAuth , AuthErrorCode } from "@revstackhq/auth" ;
// Build contract once at module level
const authContract = buildAuthContract ( "clerk" , {
issuerUrl: process . env . CLERK_ISSUER_URL ! ,
});
const auth = new RevstackAuth ( authContract );
export default async function handler (
req : NextApiRequest ,
res : NextApiResponse
) {
const session = await auth . validate ( req . headers . authorization );
if ( ! session . isValid ) {
if ( session . errorCode === AuthErrorCode . TOKEN_EXPIRED ) {
return res . status ( 401 ). json ({
error: "Session expired. Please sign in again."
});
}
return res . status ( 401 ). json ({ error: session . error });
}
// Use the verified user ID
const userData = {
userId: session . userId ,
sessionId: session . claims . sid ,
};
return res . status ( 200 ). json ( userData );
}
Frontend integration
When calling your API from a Clerk-enabled frontend:
// Using Clerk React
import { useAuth } from "@clerk/clerk-react" ;
function MyComponent () {
const { getToken } = useAuth ();
async function callAPI () {
const token = await getToken ();
const response = await fetch ( "/api/profile" , {
headers: {
Authorization: `Bearer ${ token } ` ,
},
});
return response . json ();
}
}
Multi-session support
If you’re using Clerk’s multi-session feature, each session token will have a unique sid claim:
const session = await auth . validate ( token );
if ( session . isValid ) {
// Store session ID to track which session is active
const sessionId = session . claims . sid ;
// Link billing data to specific session if needed
await linkSubscriptionToSession ( session . userId , sessionId );
}
Organization support
When using Clerk Organizations, the organization_id claim is automatically included:
const session = await auth . validate ( token );
if ( session . isValid && session . claims . organization_id ) {
// User is in an organization context
console . log ( "Org ID:" , session . claims . organization_id );
// You can link subscriptions to organizations
await getOrgSubscription ( session . claims . organization_id );
}
Environment variables
Store your Clerk configuration in environment variables:
# .env.local (Next.js)
CLERK_ISSUER_URL = https://clerk.your-domain.com
# Or for Clerk development instances:
CLERK_ISSUER_URL = https://your-app.clerk.accounts.dev
Testing
To test your integration:
Sign in via your Clerk-enabled frontend
Obtain the session token using getToken() from Clerk’s SDK
Send it in the Authorization header: Bearer <token>
Verify the token is validated correctly
Troubleshooting
”Issuer mismatch” error
Ensure your issuerUrl exactly matches the issuer in your Clerk tokens. You can decode a token at jwt.io to verify the iss claim.
”Invalid signature” error
Verify that:
The token was issued by your Clerk application
The JWKS endpoint is accessible at <issuerUrl>/.well-known/jwks.json
The token hasn’t expired or been tampered with
Development vs. production URLs
Clerk uses different issuer URLs for development and production:
Development: https://your-app.clerk.accounts.dev
Production: https://clerk.your-domain.com
Make sure you’re using the correct URL for your environment.
Next steps
Auth Overview Learn more about JWT verification in Revstack
Error Handling Handle authentication errors gracefully