.cursor/skills/workos-convex-debug/SKILL.md
Debug and troubleshoot WorkOS AuthKit authentication issues with Convex. Use when authentication fails, JWT validation errors occur, user identity returns null, email claims are missing, admin access checks fail, or sign in button does not work. Supports Netlify deployment.
npx skillsauth add get-convex/components-submissions-directory workos-convex-debugInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Always check official docs for the latest information:
- Troubleshooting: https://docs.convex.dev/auth/authkit/troubleshooting
- Convex Debugging Auth: https://docs.convex.dev/auth/debug
- WorkOS AuthKit: https://workos.com/docs/authkit
- Netlify Docs: https://docs.netlify.com/
Use this skill when you encounter:
useConvexAuth() returns isAuthenticated: falsectx.auth.getUserIdentity() returns nullRun through these checks in order:
Frontend (.env.local):
VITE_CONVEX_URL=https://your-deployment.convex.cloud
VITE_WORKOS_CLIENT_ID=client_01XXXXXXXXXXXXXXXXXX
VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
Convex Dashboard:
WORKOS_CLIENT_ID is setRun npx convex dev and look for "Convex functions ready!" message.
If you see an error about WORKOS_CLIENT_ID:
Redirect URIs:
http://localhost:5173/callback for local devCORS Origins:
http://localhost:5173 for local devJWT Template:
email claim (see Problem 2 below)Symptom: Clicking Sign In has no effect. No redirect to WorkOS.
Root cause: Using deprecated getSignInUrl() or incorrect event handler.
Solution: Use signIn() directly from the AuthKit hook:
import { useAuth } from "@workos-inc/authkit-react";
function SignInButton() {
const { signIn } = useAuth();
return (
<button
onClick={() => {
localStorage.setItem("authReturnPath", window.location.pathname);
signIn();
}}
>
Sign In
</button>
);
}
Do not use:
getSignInUrl() (deprecated)Symptom: ctx.auth.getUserIdentity() returns user but identity.email is undefined.
Root cause: WorkOS JWT templates do not include email claim by default.
Solution: Configure JWT template in WorkOS Dashboard:
{
"email": "{{user.email}}",
"name": "{{user.first_name}} {{user.last_name}}",
"picture": "{{user.profile_picture_url}}"
}
Verification: After fix, identity should include:
{
tokenIdentifier: "https://api.workos.com/user_management/client_xxx|user_yyy",
subject: "user_yyy",
issuer: "https://api.workos.com/user_management/client_xxx",
email: "[email protected]",
name: "User Name",
pictureUrl: "https://..."
}
Symptom: Authentication works for some users but not others. "Invalid token" errors.
Root cause: Only one JWT provider configured. WorkOS issues JWTs from two issuers:
https://api.workos.com/https://api.workos.com/user_management/{clientId}Solution: Configure both providers in convex/auth.config.ts:
const clientId = process.env.WORKOS_CLIENT_ID;
export default {
providers: [
{
type: "customJwt",
issuer: "https://api.workos.com/",
algorithm: "RS256",
applicationID: clientId,
jwks: `https://api.workos.com/sso/jwks/${clientId}`,
},
{
type: "customJwt",
issuer: `https://api.workos.com/user_management/${clientId}`,
algorithm: "RS256",
jwks: `https://api.workos.com/sso/jwks/${clientId}`,
},
],
};
Run npx convex dev after changing this file.
Symptom: After login, user lands on page showing "not authenticated" briefly.
Root cause: Callback component redirects before AuthKit finishes token exchange.
Solution: Wait for AuthKit loading state:
function AuthCallback() {
const { isLoading, user, signIn } = useAuth();
const [authFailed, setAuthFailed] = useState(false);
const hasAuthCode = useMemo(
() => new URLSearchParams(window.location.search).has("code"),
[]
);
useEffect(() => {
// Wait for AuthKit to finish
if (isLoading) {
return;
}
// Only redirect after session established
if (user) {
window.location.replace(returnPath);
return;
}
// Auth code present but no user = exchange failed
if (hasAuthCode) {
setAuthFailed(true);
return;
}
window.location.replace(returnPath);
}, [hasAuthCode, isLoading, returnPath, user]);
return (
<div>
{authFailed ? (
<button onClick={() => signIn()}>Try Again</button>
) : (
<div>Finishing sign in...</div>
)}
</div>
);
}
Symptom: User successfully logs in via WorkOS, but useConvexAuth() returns isAuthenticated: false.
Root cause: Backend not correctly configured to validate tokens.
Diagnostic steps:
convex/auth.config.ts exists and has both providersWORKOS_CLIENT_ID is set in Convex Dashboardnpx convex dev to deploy configCommon causes:
WORKOS_CLIENT_ID environment variableSymptom: User with @yourdomain.com email shows as non-admin.
Root cause: Usually Problem 2 (email not in JWT claims).
Debugging:
// Add logging in your admin check
export const isAdmin = query({
args: {},
returns: v.boolean(),
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
console.log("Identity:", JSON.stringify(identity, null, 2));
if (!identity) {
console.log("No identity");
return false;
}
const email = identity.email;
console.log("Email:", email);
if (!email) {
console.log("No email in identity");
return false;
}
return email.endsWith("@yourdomain.com");
},
});
Check Convex Dashboard Logs for output.
Symptom:
WorkOSPlatformNotAuthorized: Your WorkOS platform API key is not authorized
to access this team.
Root cause: WorkOS workspace has been disconnected from Convex.
Solution: Reconnect or create new workspace:
npx convex integration workos disconnect-team
npx convex integration workos provision-team
Note: You may need a different email for the new WorkOS workspace.
Symptom: Token validation fails with audience claim error.
Root cause: WorkOS JWTs may not include aud (audience) claim by default.
Solution: Check WorkOS Dashboard JWT configuration:
function DebugAuth() {
const { user, isLoading } = useAuth();
const { isAuthenticated, isLoading: convexLoading } = useConvexAuth();
console.log("AuthKit state:", { user, isLoading });
console.log("Convex state:", { isAuthenticated, convexLoading });
return null;
}
export const debugIdentity = query({
args: {},
returns: v.any(),
handler: async (ctx) => {
const identity = await ctx.auth.getUserIdentity();
console.log("Full identity:", JSON.stringify(identity, null, 2));
return identity;
},
});
View logs in Convex Dashboard > Logs.
// In your app, temporarily add:
console.log("VITE_WORKOS_CLIENT_ID:", import.meta.env.VITE_WORKOS_CLIENT_ID);
console.log("VITE_WORKOS_REDIRECT_URI:", import.meta.env.VITE_WORKOS_REDIRECT_URI);
If undefined:
VITE_.env.local.env.local is in project rootCheck Convex Dashboard > Settings > Environment Variables
If WORKOS_CLIENT_ID shows error in logs:
Development:
VITE_WORKOS_REDIRECT_URI=http://localhost:5173/callback
Netlify Production:
VITE_WORKOS_REDIRECT_URI=https://yourdomain.netlify.app/components/callback
Custom Domain Production:
VITE_WORKOS_REDIRECT_URI=https://yourdomain.com/callback
All callback URLs must be added to WorkOS Dashboard > Redirect URIs.
Add all to WorkOS Dashboard > Sessions > CORS:
http://localhost:5173https://yourdomain.netlify.apphttps://yourdomain.com (if using custom domain)Set WORKOS_CLIENT_ID separately for:
MIME type error on Netlify:
If you see Failed to load module script: Expected a JavaScript module script but the server responded with a MIME type of "text/html":
netlify.toml exists with SPA redirect:[[redirects]]
from = "/*"
to = "/index.html"
status = 200
vite.config.ts uses base: "/" for Netlify hostingCallback not working on Netlify:
.env.production matches what's in WorkOS Dashboard/components/callback)| Symptom | Quick fix |
|---------|-----------|
| Sign in does nothing | Use signIn() not getSignInUrl() |
| Email undefined | Configure JWT template in WorkOS |
| Intermittent failures | Add both JWT providers |
| Callback timing | Wait for isLoading to be false |
| Admin check fails | Check email claim in JWT template |
| Platform error | Reconnect WorkOS workspace |
If none of these solutions work:
development
Set up and configure WorkOS AuthKit authentication with Convex backend. Use when integrating AuthKit, configuring JWT providers, setting up environment variables, or implementing sign in and sign out flows with React and Vite. Supports Netlify deployment.
documentation
# Update project docs Use this skill after completing any feature, fix, or migration to keep the three core project tracking files in sync. Activate with: `@update-project-docs` ## Step 1: Get real dates Run this first: ```bash git log --date=short -n 10 ``` Use actual commit dates. Never use placeholder dates or future months. ## Step 2: Update TASK.md Move completed items into `## Completed` with date and time: ```markdown - [x] Feature name (YYYY-MM-DD HH:mm UTC) - [x] Sub-task det
tools
# Create a PRD Use this skill before any multi-file feature, architectural decision, or complex bug fix. Activate with: `@create-prd` ## Location and naming - All PRDs live in `prds/` folder - File name: `prds/<feature-or-problem-slug>.md` - Extension is always `.md`, not `.prd` - Use kebab-case for the filename (e.g., `prds/adding-email-auth.md`) ## Template Copy and fill in this template: ```markdown # [Feature or problem name] Created: YYYY-MM-DD HH:mm UTC Last Updated: YYYY-MM-DD HH:
development
Patterns for scaling read-heavy Convex apps to millions of users. Use when optimizing bandwidth, reducing query costs, fixing slow queries, creating digest tables, replacing reactive subscriptions with one-shot fetches, adding compound indexes, debouncing writes, rate-controlling backfills, or running npx convex insights. Trigger when users mention "scale", "bandwidth", "performance", "optimize", "slow queries", "expensive queries", "digest table", "denormalize", or "thundering herd" in the context of Convex.