Plan-Based Access Control
FastAWS includes automatic plan-based access control that manages user permissions based on their Stripe subscription.
Access Levels
There are 3 access levels:
- Level 1: e.g. Pro plan
- Level 2: e.g. Team plan
- Level 3: e.g. Enterprise plan
Usage Examples
1. RequiresPlan Component
Best for conditional UI rendering:
import RequiresPlan from "@/components/auth/RequiresPlan"; // Only show to paid users <RequiresPlan level={1}> <ProFeature /> </RequiresPlan> // Show upgrade prompt to free users <RequiresPlan level={1} fallback={<UpgradeButton />}> <ProFeature /> </RequiresPlan>
2. usePlanAccess Hook
Best for component logic with multiple plan checks:
import { usePlanAccess } from "@/hooks/usePlanAccess"; function FeatureDashboard() { const { accessLevel, hasMinimumAccess } = usePlanAccess(); return ( <div> {accessLevel >= 1 && <ProFeature />} {accessLevel >= 2 && <TeamFeature />} {hasMinimumAccess(3) && <EnterpriseFeature />} </div> ); }
3. Direct Access Checks
Best for simple inline conditionals:
function MyComponent() { const user = useCurrentUser(); return ( <div> {user.accessLevel >= 1 && <ProFeature />} {user.accessLevel >= 2 && <TeamFeature />} </div> ); }
4. Server-Side Access Control
For API routes:
export async function POST(request: Request) { const user = await getCurrentUser(request); if (user.accessLevel < 1) { return NextResponse.json( { error: "Pro plan required" }, { status: 403 } ); } // Process request }
How It Works
- User subscribes via Stripe checkout
- Webhook fires to your backend
- Access level calculated from Stripe price ID (configured in
app-config.ts) - DynamoDB updated with
accessLevelfield - Cognito synced with
custom:access_levelattribute - Frontend components automatically show/hide features based on access level
Configuration
Access levels are automatically determined by your Stripe price IDs in config/app-config.ts and plan definitions in config/plans.ts:
// config/plans.ts { id: "subscription_plan_1", name: "Pro Monthly", accessLevel: 1, priceId: STRIPE_PRICES.SUBSCRIPTION_PLAN_1, // ... }
The mapping from price ID to access level is handled automatically by the backend.
Best Practices
Provide Upgrade Prompts
// ✅ Good - Clear user experience <RequiresPlan level={1} fallback={<UpgradeButton targetLevel={1} />} > <ProFeature /> > </RequiresPlan> // ❌ Avoid - Features just disappear <RequiresPlan level={1}> <ProFeature /> </RequiresPlan>
Use Numeric Levels
// ✅ Good - Simple and clear <RequiresPlan level={1}> {user.accessLevel >= 2 && <TeamFeature />} // ❌ Avoid - String comparisons are error-prone {user.plan === "pro" && <ProFeature />}
Troubleshooting
User Has Wrong Access Level
- Check Stripe webhook logs in CloudWatch
- Verify user profile in DynamoDB has
accessLevelfield - Check that Stripe price ID is configured in
app-config.ts - Clear browser cache and re-login
Access Control Not Working
- Verify
user.accessLevelis set (check browser console) - Ensure components are receiving current user data
- Check that RequiresPlan is using correct level number