name: clerk description: "[Applies to: **/*] This guide establishes definitive best practices for integrating Clerk, focusing on robust security, efficient session management, and secure token handling in line with modern OAuth 2.0 (RFC 9700) and JWT (RFC 8725) standards." source: "cursor_mdc"
Clerk Best Practices
Integrating Clerk requires a security-first mindset. Authentication is a primary attack surface; therefore, all Clerk integrations MUST adhere to the strictest security standards. This guide provides actionable, opinionated best practices to ensure your application's identity layer is robust and future-proof.
1. Security Best Practices
1.1. Enforce Authorization Code Flow with PKCE
NEVER use the implicit grant flow. RFC 9700 explicitly deprecates it due to inherent security weaknesses. Always use the Authorization Code Flow with Proof Key for Code Exchange (PKCE). Clerk's client-side SDKs handle PKCE automatically when correctly configured.
❌ BAD: Relying on deprecated flows (e.g., implicit grant)
// Avoid configuring Clerk in ways that force implicit grants
// (e.g., older SDK versions or misconfigurations)
// Ensure your Clerk application settings are configured for PKCE.
✅ GOOD: Utilize Clerk's Authorization Code Flow with PKCE (default for modern SDKs)
// Clerk's modern SDKs (v5.x+) use PKCE by default.
// Ensure your Clerk application is configured to require PKCE.
// Example: Initializing Clerk in a Next.js app
import { ClerkProvider } from '@clerk/nextjs';
function MyApp({ Component, pageProps }) {
return (
<ClerkProvider {...pageProps}>
<Component {...pageProps} />
</ClerkProvider>
);
}
1.2. Centralize Authentication Logic
All authentication and authorization checks MUST occur server-side and be handled by a centralized, security-vetted library (Clerk's backend SDKs). Never perform critical authentication decisions solely on the client.
❌ BAD: Client-side authorization decisions
// In a React component:
if (clerk.user.publicMetadata.role === 'admin') {
// Render admin-only content
}
// This is easily bypassed by manipulating client-side state.
✅ GOOD: Server-side authorization with Clerk's backend SDKs
// In a Next.js API route or server component:
import { getAuth } from '@clerk/nextjs/server';
import { NextApiRequest, NextApiResponse } from 'next';
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { userId, orgRole } = getAuth(req);
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
if (orgRole !== 'admin') {
return res.status(403).json({ error: 'Forbidden' });
}
// Proceed with admin-only logic
res.status(200).json({ message: 'Welcome, admin!' });
}
1.3. Securely Store API Keys and Secrets
API keys, client secrets, and Clerk private keys MUST NEVER be hardcoded or stored directly in source code or configuration files. Use a managed Secrets Manager (e.g., AWS Secrets Manager, Azure Key Vault, HashiCorp Vault) and reference them at runtime via environment variables.
❌ BAD: Hardcoding secrets
// .env
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY='pk_test_YOUR_PUBLISHABLE_KEY' // OK for client-side
CLERK_SECRET_KEY='sk_test_YOUR_SECRET_KEY_HARDCODED' // DANGER!
// app/api/webhook.ts
const clerkSecretKey = 'sk_test_YOUR_SECRET_KEY_HARDCODED'; // NEVER DO THIS
✅ GOOD: Using environment variables and a Secrets Manager
// .env.local (for local development, not committed)
CLERK_SECRET_KEY=${CLERK_SECRET_KEY_FROM_SECRETS_MANAGER}
// In production, CLERK_SECRET_KEY is injected from your Secrets Manager.
// app/api/webhook.ts
const clerkSecretKey = process.env.CLERK_SECRET_KEY;
if (!clerkSecretKey) {
throw new Error('CLERK_SECRET_KEY is not set.');
}
// Use clerkSecretKey for server-side operations
1.4. Enforce HTTPS Everywhere
All communication involving authentication credentials or sensitive data MUST occur over HTTPS. This is non-negotiable. Configure your application and infrastructure to redirect all HTTP traffic to HTTPS.
❌ BAD: Allowing HTTP connections
http://your-app.com/login // Insecure
✅ GOOD: Requiring HTTPS
https://your-app.com/login // Secure
1.5. Implement Strong Password Policies and MFA
Leverage Clerk's robust password policy configuration to enforce NIST SP 800-63B guidelines:
- Minimum length: 8 characters with MFA, 15 without MFA.
- Maximum length: At least 64 characters (allow passphrases).
- Allow all characters (unicode, whitespace).
- Block common/breached passwords (Clerk integrates with services like HaveIBeenPwned).
- Enable Multi-Factor Authentication (MFA) for all users, especially for high-risk actions or sensitive accounts.
❌ BAD: Weak password policy
// Clerk dashboard configured for:
// Minimum password length: 6
// No MFA required
✅ GOOD: Robust password policy and MFA
// Clerk dashboard configured for:
// Minimum password length: 15 (or 8 with mandatory MFA)
// MFA required for all users
// Password complexity rules enabled
// Breached password detection enabled
1.6. Validate JWTs Rigorously
When handling Clerk's JWT-based sessions (e.g., in custom backend services), always validate the token's signature, audience (aud), issuer (iss), and expiration (exp). Use strong signing algorithms (RS256 or ES256). Clerk's backend SDKs handle this automatically; avoid manual JWT parsing and validation unless absolutely necessary and with extreme caution.
❌ BAD: Trusting JWTs without full validation
// In a custom backend:
const token = req.headers.authorization?.split(' ')[1];
// const decoded = jwt.decode(token); // DANGER: Does not verify signature or claims
// if (decoded.userId) { /* grant access */ }
✅ GOOD: Using Clerk's verified session/token validation
// In a custom backend (e.g., Express middleware):
import { ClerkExpressWithAuth } from '@clerk/clerk-sdk-node';
app.use(
ClerkExpressWithAuth({
jwtKey: process.env.CLERK_JWT_VERIFICATION_KEY, // Use JWKS or Clerk's provided key
}),
(req, res, next) => {
// req.auth will contain validated userId, sessionId, etc.
if (!req.auth.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
);
2. Session Management and Token Handling
2.1. Enforce Short-Lived Access Tokens
Configure Clerk sessions to use short-lived access tokens. This minimizes the window of opportunity for token compromise. Refresh tokens should be used to obtain new access tokens.
❌ BAD: Long-lived access tokens
// Clerk session configuration allowing very long access token lifetimes (e.g., days)
✅ GOOD: Short-lived access tokens
// Clerk session configuration with access token lifetimes in minutes/hours, not days.
// Clerk handles refresh token rotation automatically for its SDKs.
2.2. Keep Clerk SDKs Up-to-Date
Regularly update your Clerk client and server SDKs to the latest stable versions. This ensures you benefit from security patches, performance improvements, and adherence to the latest best practices. As of early 2025, the CDN release is 5.62.0.
❌ BAD: Sticking to old SDK versions
// package.json
"dependencies": {
"@clerk/nextjs": "^4.x.x", // Outdated, missing critical updates
}
✅ GOOD: Updating Clerk SDKs regularly
// package.json
"dependencies": {
"@clerk/nextjs": "^5.x.x", // Always use the latest major/minor version
}
// Run `npm update @clerk/nextjs` or `yarn upgrade @clerk/nextjs`
3. Code Organization and Vulnerability Prevention
3.1. Isolate Clerk Initialization
Initialize Clerk at the highest possible level in your application (e.g., _app.js in Next.js, main.tsx in React). This ensures consistent behavior and avoids re-initialization issues.
❌ BAD: Multiple Clerk initializations or conditional loading
// In multiple components:
// const clerk = new Clerk('pk_...'); // Inefficient and error-prone
✅ GOOD: Single, top-level Clerk initialization
// pages/_app.tsx (Next.js)
import { ClerkProvider } from '@clerk/nextjs';
import type { AppProps } from 'next/app';
function MyApp({ Component, pageProps }: AppProps) {
return (
<ClerkProvider {...pageProps}>
<Component {...pageProps} />
</ClerkProvider>
);
}
export default MyApp;
3.2. Implement Automated Security Scans and Code Reviews
Integrate static analysis tools, linters, and dependency scanners into your CI/CD pipeline. Require mandatory code reviews for any changes touching authentication flows, Clerk integration, or security-sensitive areas.
❌ BAD: Manual, ad-hoc security checks
// Relying solely on manual testing for security vulnerabilities.
✅ GOOD: Automated and human-verified security
# .github/workflows/ci.yml
name: CI/CD Pipeline
jobs:
security-scan:
steps:
- uses: actions/checkout@v3
- name: Run ESLint
run: npm run lint
- name: Run Snyk/Dependabot
run: snyk test # or dependabot/npm_and_yarn
# ... other security checks