Authentication Implementation Guide
This guide provides practical implementation examples for working with the MyStoryFlow authentication system across all applications in the monorepo.
🚀 Quick Start
1. Basic Authentication Setup
// app/layout.tsx
import { AuthProvider } from '@mystoryflow/auth'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<body>
<AuthProvider
supabaseUrl={process.env.NEXT_PUBLIC_SUPABASE_URL!}
supabaseAnonKey={process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!}
>
{children}
</AuthProvider>
</body>
</html>
)
}
``` -->
### 2. Using Authentication in Components
```tsx
// components/UserProfile.tsx
import { useAuth } from '@mystoryflow/auth'
export function UserProfile() {
const { user, profile, loading, signOut } = useAuth()
if (loading) {
return <div>Loading...</div>
}
if (!user) {
return <div>Please sign in</div>
}
return (
<div className="p-4">
<h1>Welcome, {profile?.full_name || user.email}</h1>
<p>Role: {profile?.role}</p>
<button onClick={signOut} className="btn btn-secondary">
Sign Out
</button>
</div>
)
}
``` -->
### 3. Protected Routes
```tsx
// components/ProtectedRoute.tsx
import { ProtectedRoute } from '@mystoryflow/auth'
export function AdminPage() {
return (
<ProtectedRoute requiredRole="admin">
<div>
<h1>Admin Dashboard</h1>
{/* Admin content */}
</div>
</ProtectedRoute>
)
}
``` -->
## 🔐 Authentication Patterns
### Login Form Implementation
```tsx
// components/LoginForm.tsx
import { useState } from 'react'
import { useAuth } from '@mystoryflow/auth'
import { useRouter } from 'next/navigation'
export function LoginForm() {
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [loading, setLoading] = useState(false)
const [error, setError] = useState('')
const { signIn } = useAuth()
const router = useRouter()
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setLoading(true)
setError('')
try {
const { error } = await signIn(email, password)
if (error) {
setError(error.message)
} else {
router.push('/dashboard')
}
} catch (err) {
setError('An unexpected error occurred')
} finally {
setLoading(false)
}
}
return (
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="email">Email</label>
<input
id="email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
className="w-full p-2 border rounded"
/>
</div>
<div>
<label htmlFor="password">Password</label>
<input
id="password"
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
className="w-full p-2 border rounded"
/>
</div>
{error && <div className="text-red-600 text-sm">{error}</div>}
<button
type="submit"
disabled={loading}
className="w-full p-2 bg-blue-600 text-white rounded disabled:opacity-50"
>
{loading ? 'Signing in...' : 'Sign In'}
</button>
</form>
)
}
``` -->
## 🔄 Cross-App Navigation
### Admin Portal Link
```tsx
// components/AdminPortalLink.tsx
import { useAuth } from '@mystoryflow/auth'
import { createSessionBridge } from '@mystoryflow/auth'
export function AdminPortalLink() {
const { user, profile } = useAuth()
const openAdminPortal = async () => {
if (!user || !['admin', 'super_admin'].includes(profile?.role)) {
alert('Admin access required')
return
}
try {
const sessionBridge = createSessionBridge(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
const currentSession = await sessionBridge.getCurrentSession()
if (currentSession) {
const adminUrl = process.env.NEXT_PUBLIC_ADMIN_APP_URL!
const secureUrl = sessionBridge.createCrossDomainUrl(
adminUrl,
currentSession
)
window.open(secureUrl, '_blank')
}
} catch (error) {
console.error('Failed to open admin portal:', error)
alert('Failed to open admin portal')
}
}
if (!user || !['admin', 'super_admin'].includes(profile?.role)) {
return null
}
return (
<button
onClick={openAdminPortal}
className="px-4 py-2 bg-purple-600 text-white rounded hover:bg-purple-700"
>
Open Admin Portal
</button>
)
}
``` -->
## 🎛️ Admin Dashboard Implementation
### User Management Component
```tsx
// components/admin/UserManagement.tsx
import { useState, useEffect } from 'react'
import { userManagementService } from '@/lib/user-management-service'
export function UserManagement() {
const [users, setUsers] = useState([])
const [loading, setLoading] = useState(true)
const [searchQuery, setSearchQuery] = useState('')
useEffect(() => {
loadUsers()
}, [searchQuery])
const loadUsers = async () => {
try {
setLoading(true)
const result = await userManagementService.searchUsers({
query: searchQuery,
limit: 50,
})
setUsers(result.users)
} catch (error) {
console.error('Failed to load users:', error)
} finally {
setLoading(false)
}
}
const handleImpersonate = async (userId: string) => {
try {
const session = await userImpersonationService.startImpersonation({
targetUserId: userId,
reason: 'Admin support',
duration: 30 * 60 * 1000, // 30 minutes
})
const webAppUrl = `${process.env.NEXT_PUBLIC_WEB_APP_URL}?impersonation_token=${session.token}`
window.open(webAppUrl, '_blank')
} catch (error) {
console.error('Impersonation failed:', error)
alert('Failed to start impersonation')
}
}
return (
<div className="space-y-6">
<input
type="text"
placeholder="Search users..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="w-full p-2 border rounded"
/>
{loading ? (
<div>Loading users...</div>
) : (
<div className="grid gap-4">
{users.map((user) => (
<div
key={user.id}
className="p-4 border rounded flex justify-between items-center"
>
<div>
<h3 className="font-medium">{user.full_name}</h3>
<p className="text-sm text-gray-600">{user.email}</p>
<span className="inline-block px-2 py-1 text-xs bg-blue-100 text-blue-800 rounded">
{user.role}
</span>
</div>
<div className="space-x-2">
<button
onClick={() => handleImpersonate(user.id)}
className="px-3 py-1 bg-yellow-600 text-white rounded text-sm"
>
Impersonate
</button>
</div>
</div>
))}
</div>
)}
</div>
)
}
``` -->
## 🎭 User Impersonation Implementation
### Impersonation Banner (Web App)
```tsx
// components/ImpersonationBanner.tsx
import { useState, useEffect } from 'react'
import { userImpersonationService } from '@/lib/user-impersonation-service'
import { AlertTriangle } from 'lucide-react'
export function ImpersonationBanner() {
const [session, setSession] = useState(null)
useEffect(() => {
checkImpersonation()
}, [])
const checkImpersonation = async () => {
const urlParams = new URLSearchParams(window.location.search)
const token = urlParams.get('impersonation_token')
if (token) {
try {
const validSession =
await userImpersonationService.validateSession(token)
if (validSession) {
setSession(validSession)
sessionStorage.setItem(
'impersonation_session',
JSON.stringify(validSession)
)
window.history.replaceState({}, '', window.location.pathname)
}
} catch (error) {
console.error('Invalid impersonation token:', error)
}
}
}
const handleEndImpersonation = async () => {
if (session) {
try {
await userImpersonationService.endImpersonation(session.id, 'manual')
setSession(null)
sessionStorage.removeItem('impersonation_session')
window.location.href = '/login'
} catch (error) {
console.error('Failed to end impersonation:', error)
}
}
}
if (!session) return null
return (
<div className="bg-yellow-500 text-yellow-900 px-4 py-2 text-center">
<div className="flex items-center justify-center gap-4">
<AlertTriangle className="h-5 w-5" />
<span>
You are being impersonated by an administrator. Session expires at{' '}
{new Date(session.expiresAt).toLocaleTimeString()}
</span>
<button
onClick={handleEndImpersonation}
className="px-3 py-1 bg-yellow-700 text-yellow-100 rounded text-sm"
>
End Session
</button>
</div>
</div>
)
}
``` -->
## 🔧 Development Tips
### Environment Setup
```bash
# Install dependencies
npm install
# Set up environment variables
cp .env.example .env.local
# Start development servers
npm run dev
``` -->
### Common Patterns
```tsx
// Custom hook for admin checks
export function useIsAdmin() {
const { profile } = useAuth()
return ['admin', 'super_admin'].includes(profile?.role)
}
// Role-based rendering
export function RoleGuard({ role, children, fallback = null }) {
const { profile } = useAuth()
if (profile?.role !== role) {
return fallback
}
return children
}
``` -->
## 🚨 Troubleshooting
### Common Issues
1. **Session not persisting across apps**
- Check environment variables are set correctly
- Verify Supabase configuration
- Check browser console for errors
2. **Admin dashboard not accessible**
- Verify user role in database
- Check admin permissions
- Review authentication flow
3. **Impersonation not working**
- Check service role permissions
- Verify JWT secret configuration
- Review audit logs
This implementation guide provides practical examples for integrating the authentication system across your MyStoryFlow applications.