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

  1. User subscribes via Stripe checkout
  2. Webhook fires to your backend
  3. Access level calculated from Stripe price ID (configured in app-config.ts)
  4. DynamoDB updated with accessLevel field
  5. Cognito synced with custom:access_level attribute
  6. 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

  1. Check Stripe webhook logs in CloudWatch
  2. Verify user profile in DynamoDB has accessLevel field
  3. Check that Stripe price ID is configured in app-config.ts
  4. Clear browser cache and re-login

Access Control Not Working

  1. Verify user.accessLevel is set (check browser console)
  2. Ensure components are receiving current user data
  3. Check that RequiresPlan is using correct level number