Auth & RBAC
"I'm signed in but getting 401"
Most common cause: the request didn't carry credentials.
- Browser fetch: ensure
credentials: 'include'. - Agent backend → marketplace: ensure
Authorization: Bearer <access_token>is present. - curl: include
-b cookies.txtor-H "Authorization: Bearer ...".
Next: check the cookie hasn't been silently dropped.
- DevTools → Application → Cookies → look for
better-auth-session. - If absent, the response that set it had
SameSite=Laxbut the request was cross-origin → the browser dropped it. See CORS and cookies.
Next: the session might be expired.
- Default better-auth session is 30 days, refreshed on use.
- If
AUTH_SECRETwas rotated, every existing session is invalidated.
"Getting 403 FORBIDDEN on /admin/* endpoints"
Your user isn't platform_admin. Verify:
curl -b cookies.txt https://api.fleapo.ai/api/auth/get-session | jq '.user.role'
If it's org_member, you need to be promoted. Another admin must call:
POST /api/auth/admin/set-role
{ "userId": "<id>", "role": "platform_admin" }
If you ARE platform_admin and STILL get 403, the statement in src/auth/permissions.ts is probably missing the action that better-auth's admin plugin is checking. Add the action and redeploy. See RBAC → "Critical: statement must be exhaustive".
"Frontend shows admin UI but backend returns 403"
The frontend's src/lib/permissions.ts is out of sync with the backend. The two MUST be byte-for-byte identical (modulo imports). Diff them:
diff marketplace-fleapoai-service/src/auth/permissions.ts fleapo-marketplace/src/lib/permissions.ts
Fix the frontend file to match the backend.
"Quota consume returns 402 PAYMENT_REQUIRED"
The user has no active subscription on this agent. Either:
- They never subscribed.
- They cancelled.
- Their status is
past_due/incomplete/canceled.
Check:
SELECT status, current_period_end
FROM user_agent_subscriptions
WHERE user_id = '<uid>' AND agent_id = '<aid>';
If they should have a subscription:
- Did Stripe webhook fail? Check
stripe_eventsfor unprocessed events. - Did checkout complete? Inspect
interactionsforsubscription_created.
"401 with details.signInUrl set"
That's the marketplace's standard 401 envelope. Redirect the user to details.signInUrl. Don't bury this in a generic toast.
"RBAC role doesn't update after I change it"
Roles are in the session token, refreshed on every request. After POST /api/auth/admin/set-role:
- The user's existing session may still carry the old role until next session read.
- For OAuth bearer tokens, the role is frozen in the ID token (10h). Wait for refresh.
If you need instantaneous effect, revoke the user's sessions:
POST /api/auth/admin/revoke-sessions
{ "userId": "<id>" }
They'll be signed out everywhere.
"Sign-in returns 200 but useMe() returns null"
The session cookie is set but the SPA isn't including it. Check:
fetch(..., { credentials: 'include' })everywhere (orval-mutator handles this for the generated client).- CORS allows
credentials: true. FRONTEND_URLmatches the actual origin.