-
Notifications
You must be signed in to change notification settings - Fork 13
(DO NOT MERGE) feat: update starter kit with route protection and additional guides #28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
f13da15
2c77420
7259f60
8506824
831eebb
e7172a6
5b20730
050b5ca
6fb7c63
2848933
c43594e
f2e5bf0
9624e19
53e4518
03c736a
c8ef71f
412729f
97cdc75
ee9fe64
14e997c
a8a6523
bc502d1
816172f
c04f4d7
a2ae630
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,79 @@ | ||
| # Authentication Setup Guide | ||
|
|
||
| This React Starter Kit includes optional authentication functionality that can be enabled or disabled using environment variables. | ||
|
|
||
| ## Quick Start | ||
|
|
||
| ### 1. Enable Authentication | ||
|
|
||
| To enable authentication, create a `.env` file in the root directory and set: | ||
|
|
||
| ```bash | ||
| VITE_ENABLE_AUTH=true | ||
| ``` | ||
|
|
||
| ### 2. Configure Kinde Authentication | ||
|
|
||
| You'll also need to set up your Kinde authentication credentials: | ||
|
|
||
| ```bash | ||
| VITE_KINDE_CLIENT_ID=your_kinde_client_id | ||
| VITE_KINDE_DOMAIN=https://your_subdomain.kinde.com | ||
| VITE_KINDE_REDIRECT_URL=http://localhost:3000 | ||
| VITE_KINDE_LOGOUT_URL=http://localhost:3000 | ||
| ``` | ||
|
|
||
| ### 3. Disable Authentication | ||
|
|
||
| To disable authentication, set: | ||
|
|
||
| ```bash | ||
| VITE_ENABLE_AUTH=false | ||
| ``` | ||
|
|
||
| ## How It Works | ||
|
|
||
| - **Authentication Disabled**: Shows a simple welcome page without any auth components | ||
| - **Authentication Enabled**: Shows login/signup forms and user dashboard with full authentication flow | ||
|
|
||
| ## Features | ||
|
|
||
| When authentication is enabled: | ||
|
|
||
| - **LoggedOut Component**: Shows navbar with Sign In and Sign Up buttons | ||
| - **LoggedIn Component**: Shows navbar with user profile, account management, and logout | ||
| - **Responsive Design**: Built with Tailwind CSS for modern, mobile-friendly UI | ||
| - **User Profile**: Displays user information including name, email, and profile picture | ||
|
|
||
| ## Environment Variables | ||
|
|
||
| | Variable | Description | Default | | ||
| | ------------------------- | ----------------------------- | -------------------------- | | ||
| | `VITE_ENABLE_AUTH` | Enable/disable authentication | `false` | | ||
| | `VITE_KINDE_CLIENT_ID` | Kinde client ID | Required when auth enabled | | ||
| | `VITE_KINDE_DOMAIN` | Kinde domain | Required when auth enabled | | ||
| | `VITE_KINDE_REDIRECT_URL` | Redirect URL after auth | Required when auth enabled | | ||
| | `VITE_KINDE_LOGOUT_URL` | Logout redirect URL | Required when auth enabled | | ||
|
|
||
| ## Getting Started with Kinde | ||
|
|
||
| 1. Sign up at [Kinde](https://kinde.com) | ||
| 2. Create a new application | ||
| 3. Get your client ID and domain | ||
| 4. Configure your redirect URLs | ||
| 5. Update your `.env` file with the credentials | ||
|
|
||
| ## Development | ||
|
|
||
| ```bash | ||
| # Install dependencies | ||
| npm install | ||
|
|
||
| # Start development server | ||
| npm run dev | ||
|
|
||
| # Build for production | ||
| npm run build | ||
| ``` | ||
|
|
||
| The authentication feature is completely optional and won't affect your app's functionality when disabled. |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,232 @@ | ||
| # Enhanced ProtectedRoute Component | ||
|
|
||
| The `ProtectedRoute` component has been enhanced to support custom access control beyond basic authentication. This allows you to implement role-based access control, feature flags, and other permission-based routing. | ||
|
|
||
| ## Basic Usage | ||
|
|
||
| ```tsx | ||
| import ProtectedRoute from "./components/ProtectedRoute"; | ||
|
|
||
| // Basic authentication only | ||
| <ProtectedRoute> | ||
| <YourComponent /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ## Advanced Usage with Access Control | ||
|
|
||
| ### Using KindeAuth Permissions | ||
|
|
||
| ```tsx | ||
| // Check for specific permissions | ||
| <ProtectedRoute has={{ permissions: ['admin'] }} fallbackPath="/home"> | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Using KindeAuth Roles | ||
|
|
||
| ```tsx | ||
| <ProtectedRoute | ||
| has={{ roles: ['manager'] }} | ||
| fallbackPath="/upgrade" | ||
| > | ||
| <ManagerContent /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ## Access Control Examples | ||
|
|
||
| ### Permission-Based Access | ||
|
|
||
| ```tsx | ||
| // Check for admin permissions | ||
| <ProtectedRoute has={{ permissions: ['admin'] }}> | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
|
|
||
| // Check for multiple permissions | ||
| <ProtectedRoute has={{ permissions: ['read', 'write'] }}> | ||
| <Editor /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Role-Based Access | ||
|
|
||
| ```tsx | ||
| // Check for specific role | ||
| <ProtectedRoute has={{ roles: ['manager'] }}> | ||
| <ManagerDashboard /> | ||
| </ProtectedRoute> | ||
|
|
||
| // Check for multiple roles | ||
| <ProtectedRoute has={{ roles: ['admin', 'supervisor'] }}> | ||
| <AdminDashboard /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Feature Flag Access | ||
|
|
||
| ```tsx | ||
| // Check for feature flags | ||
| <ProtectedRoute has={{ featureFlags: ['beta-feature'] }}> | ||
| <BetaFeature /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Billing Entitlements | ||
|
|
||
| ```tsx | ||
| // Check for billing entitlements | ||
| <ProtectedRoute has={{ billingEntitlements: ['premium'] }}> | ||
| <PremiumContent /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ## Integration with KindeAuth Features | ||
|
|
||
| The `ProtectedRoute` component now directly integrates with KindeAuth's `has()` function, making it easier to check permissions, roles, feature flags, and billing entitlements without writing custom access functions. | ||
|
|
||
| ### Using KindeAuth Permissions | ||
|
|
||
| ```tsx | ||
| // Check for specific permissions | ||
| <ProtectedRoute has={{ permissions: ['read:admin', 'write:admin'] }}> | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Using KindeAuth Roles | ||
|
|
||
| ```tsx | ||
| // Check for specific roles | ||
| <ProtectedRoute has={{ roles: ['admin', 'supervisor'] }}> | ||
| <AdminDashboard /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Using Feature Flags | ||
|
|
||
| ```tsx | ||
| // Check for feature flags | ||
| <ProtectedRoute has={{ featureFlags: ['beta-feature', 'new-ui'] }}> | ||
| <BetaFeature /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ### Using Billing Entitlements | ||
|
|
||
| ```tsx | ||
| // Check for billing entitlements | ||
| <ProtectedRoute has={{ billingEntitlements: ['premium', 'enterprise'] }}> | ||
| <PremiumContent /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| ## Component Props | ||
|
|
||
| | Prop | Type | Required | Default | Description | | ||
| |------|------|----------|---------|-------------| | ||
| | `children` | `React.ReactNode` | Yes | - | The component to render if access is granted | | ||
| | `has` | `HasParams` | No | - | KindeAuth has() parameters to check permissions/roles | | ||
| | `fallbackPath` | `string` | No | `"/"` | Path to redirect to if access is denied | | ||
|
|
||
| ## HasParams Type | ||
|
|
||
| The `has` prop accepts the same parameters as KindeAuth's `has()` function: | ||
|
|
||
| ```tsx | ||
| interface HasParams { | ||
| roles?: string[]; | ||
| permissions?: string[]; | ||
| featureFlags?: string[]; | ||
| billingEntitlements?: string[]; | ||
| forceApi?: boolean | { | ||
| roles?: boolean; | ||
| permissions?: boolean; | ||
| featureFlags?: boolean; | ||
| billingEntitlements?: true; | ||
| }; | ||
| } | ||
| ``` | ||
|
|
||
| ## Best Practices | ||
|
|
||
| 1. **Always provide fallback paths** for better user experience | ||
| 2. **Use descriptive access function names** for better code readability | ||
| 3. **Handle undefined users gracefully** in your access functions | ||
| 4. **Combine multiple conditions** when needed for complex access control | ||
| 5. **Test your access functions** with different user scenarios | ||
|
|
||
| ## Example Implementation | ||
|
|
||
| ```tsx | ||
| // In your App.tsx or router configuration | ||
| <Routes> | ||
| <Route path="/" element={<PublicPage />} /> | ||
|
|
||
| <Route | ||
| path="/dashboard" | ||
| element={ | ||
| <ProtectedRoute> | ||
| <Dashboard /> | ||
| </ProtectedRoute> | ||
| } | ||
| /> | ||
|
|
||
| <Route | ||
| path="/admin" | ||
| element={ | ||
| <ProtectedRoute | ||
| has={{ permissions: ['admin'] }} | ||
| fallbackPath="/dashboard" | ||
| > | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
| } | ||
| /> | ||
|
|
||
| <Route | ||
| path="/premium" | ||
| element={ | ||
| <ProtectedRoute | ||
| has={{ billingEntitlements: ['premium'] }} | ||
| fallbackPath="/upgrade" | ||
| > | ||
| <PremiumContent /> | ||
| </ProtectedRoute> | ||
| } | ||
| /> | ||
| </Routes> | ||
| ``` | ||
|
|
||
| ## Testing Access Control | ||
|
|
||
| You can test different access scenarios by: | ||
|
|
||
| 1. **Setting up permissions and roles** in your KindeAuth dashboard | ||
| 2. **Creating test users** with specific permissions/roles | ||
| 3. **Using the examples page** at `/examples` to see different access controls in action | ||
| 4. **Testing feature flags** by enabling/disabling them in KindeAuth | ||
|
|
||
| ## Migration from Basic ProtectedRoute | ||
|
|
||
| If you were using the basic `ProtectedRoute` before, your existing code will continue to work without changes. The new `has` and `fallbackPath` props are optional. | ||
|
|
||
| ## Migration from Custom Access Functions | ||
|
|
||
| If you were using custom access functions with the `access` prop, you can now use the `has` prop instead: | ||
|
|
||
| **Before:** | ||
| ```tsx | ||
| <ProtectedRoute access={(user) => user?.email?.includes('admin') || false}> | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
| ``` | ||
|
|
||
| **After:** | ||
| ```tsx | ||
| <ProtectedRoute has={{ permissions: ['admin'] }}> | ||
| <AdminPanel /> | ||
| </ProtectedRoute> | ||
| ``` | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| { | ||
| "$schema": "https://ui.shadcn.com/schema.json", | ||
| "style": "new-york", | ||
| "rsc": false, | ||
| "tsx": true, | ||
| "tailwind": { | ||
| "config": "", | ||
| "css": "src/index.css", | ||
| "baseColor": "neutral", | ||
| "cssVariables": true, | ||
| "prefix": "" | ||
| }, | ||
| "aliases": { | ||
| "components": "@/components", | ||
| "utils": "@/lib/utils", | ||
| "ui": "@/components/ui", | ||
| "lib": "@/lib", | ||
| "hooks": "@/hooks" | ||
| }, | ||
| "iconLibrary": "lucide" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| import { clsx, type ClassValue } from "clsx"; | ||
| import { twMerge } from "tailwind-merge"; | ||
|
|
||
| export function cn(...inputs: ClassValue[]): string { | ||
| return twMerge(clsx(inputs)); | ||
| } | ||
|
Comment on lines
+1
to
+6
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainAvoid duplicating cn; keep a single source of truth under src/lib/utils.ts You now have two identical cn implementations (here and in src/lib/utils.ts). This risks drift and confusing imports (plain vs "@/lib/utils"). Prefer one canonical module and re-export (or remove this file). Apply one of the following: Option A — re-export to preserve any existing imports: -import { clsx, type ClassValue } from "clsx";
-import { twMerge } from "tailwind-merge";
-
-export function cn(...inputs: ClassValue[]): string {
- return twMerge(clsx(inputs));
-}
+// Deprecated: prefer importing from "@/lib/utils"
+export { cn } from "@/lib/utils";Option B — delete this file and update any imports to "@/lib/utils". Run to find call sites that may still import from lib/utils: 🏁 Script executed: #!/usr/bin/env bash
rg -nP --type ts --type tsx -C1 $'from\\s+[\'"](?:\\.?\\.?/)?lib/utils[\'"]|from\\s+[\'"]@/lib/utils[\'"]'Length of output: 139 Here’s an updated search that skips the broken 🏁 Script executed: #!/usr/bin/env bash
# Look for imports of either relative lib/utils or the canonical @/lib/utils
rg -n -C1 -g '*.ts' -g '*.tsx' "from\s+['\"](?:\.\.?\/)?lib/utils['\"]|from\s+['\"]@/lib/utils['\"]"Length of output: 259 Here’s a simpler, fixed-string search that should catch any remaining imports of 🏁 Script executed: #!/usr/bin/env bash
# Search .ts/.tsx files for literal imports of lib/utils or @/lib/utils
rg -n -C1 -g '*.ts' -g '*.tsx' \
-e "from 'lib/utils'" \
-e "from \"lib/utils\"" \
-e "from '@/lib/utils'" \
-e "from \"@/lib/utils\""Length of output: 136 🏁 Script executed: #!/usr/bin/env bash
# Search for any references to lib/utils or src/lib/utils in TypeScript files
rg -n -C1 -g '*.ts' -g '*.tsx' -e 'lib/utils' -e 'src/lib/utils'Length of output: 195 Consolidate The codebase currently has two identical
Please consolidate by choosing one of the following: Option A — Re-export the canonical implementation to preserve existing imports and mark this file deprecated: --- a/lib/utils.ts
+++ b/lib/utils.ts
@@ -1,6 +1,3 @@
- import { clsx, type ClassValue } from "clsx";
- import { twMerge } from "tailwind-merge";
-
- export function cn(...inputs: ClassValue[]): string {
- return twMerge(clsx(inputs));
- }
+ // Deprecated: use the implementation in "@/lib/utils"
+ export { cn } from "@/lib/utils";Then update call sites like -import { cn } from "../../lib/utils";
+import { cn } from "@/lib/utils";Option B — Delete Either approach ensures there’s one canonical 🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix type bug: forceApi.billingEntitlements should be boolean, not the literal type
trueThis would mislead consumers and breaks accurate typing.
Apply:
interface HasParams { roles?: string[]; permissions?: string[]; featureFlags?: string[]; billingEntitlements?: string[]; forceApi?: boolean | { roles?: boolean; permissions?: boolean; featureFlags?: boolean; - billingEntitlements?: true; + billingEntitlements?: boolean; }; }📝 Committable suggestion
🧰 Tools
🪛 LanguageTool
[grammar] ~130-~130: There might be a mistake here.
Context: ...mponent to render if access is granted | |
has|HasParams| No | - | KindeAu...(QB_NEW_EN)
[grammar] ~131-~131: There might be a mistake here.
Context: ... parameters to check permissions/roles | |
fallbackPath|string| No |"/"...(QB_NEW_EN)
🤖 Prompt for AI Agents