Skip to main content

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.txt or -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=Lax but 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_SECRET was 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_events for unprocessed events.
  • Did checkout complete? Inspect interactions for subscription_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_URL matches the actual origin.