UNPKG

@gsarthak783/accesskit-react

Version:

React SDK for AccessKit Authentication System

747 lines (604 loc) โ€ข 18.2 kB
# @gsarthak783/accesskit-react React SDK for AccessKit Authentication System - Ready-to-use hooks and components for React applications. [![npm version](https://badge.fury.io/js/@gsarthak783%2Faccesskit-react.svg)](https://badge.fury.io/js/@gsarthak783%2Faccesskit-react) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT) ## ๐Ÿš€ Quick Start ### Installation ```bash npm install @gsarthak783/accesskit-react @gsarthak783/accesskit-auth ``` ### Basic Usage Wrap your app with the `AuthProvider`: ```jsx import { AuthProvider } from '@gsarthak783/accesskit-react'; function App() { const authConfig = { projectId: 'your-project-id', apiKey: 'your-api-key' }; return ( <AuthProvider config={authConfig}> {/* Your app components */} </AuthProvider> ); } ``` ### Using Auth in Components ```jsx import { useAuth } from '@gsarthak783/accesskit-react'; function MyComponent() { const { user, isAuthenticated, isLoading, login, logout } = useAuth(); if (isLoading) { return <div>Loading...</div>; } if (!isAuthenticated) { return ( <button onClick={() => login('user@example.com', 'password')}> Login </button> ); } return ( <div> <h1>Welcome, {user.firstName}!</h1> <button onClick={logout}>Logout</button> </div> ); } ``` ### Persistent Authentication The SDK automatically maintains authentication state across page refreshes, similar to Firebase Auth. ```jsx function App() { const { user, isAuthenticated, isLoading } = useAuth(); // On initial load and page refresh: // 1. isLoading is true while checking stored tokens // 2. If valid tokens exist, user is automatically logged in // 3. isLoading becomes false once check is complete if (isLoading) { return <div>Checking authentication...</div>; } return ( <div> {isAuthenticated ? ( <div> Welcome back, {user.firstName}! {/* User stays logged in even after page refresh */} </div> ) : ( <div>Please log in</div> )} </div> ); } ``` #### How Persistence Works 1. **Token Storage**: Access and refresh tokens are stored in localStorage by default 2. **Automatic Verification**: On app initialization, stored tokens are verified 3. **Seamless Experience**: Valid tokens = user stays logged in across sessions 4. **Token Refresh**: Expired access tokens are automatically refreshed using the refresh token #### Example: Protected Routes with Persistence ```jsx function ProtectedRoute({ children }) { const { isAuthenticated, isLoading } = useAuth(); if (isLoading) { // Important: Show loading while checking auth state return <LoadingSpinner />; } if (!isAuthenticated) { return <Navigate to="/login" />; } return children; } // Usage function App() { return ( <AuthProvider config={config}> <Routes> <Route path="/login" element={<Login />} /> <Route path="/dashboard" element={ <ProtectedRoute> <Dashboard /> </ProtectedRoute> } /> </Routes> </AuthProvider> ); } ``` #### Handling Different Scenarios ```jsx function AuthStatus() { const { user, isAuthenticated, isLoading, logout } = useAuth(); useEffect(() => { if (isAuthenticated && user) { console.log('User restored from previous session:', user.email); } }, [isAuthenticated, user]); if (isLoading) { return <div>Loading...</div>; } if (!isAuthenticated) { return <div>Not logged in</div>; } return ( <div> <p>Logged in as: {user.email}</p> <p>Session persists across refreshes</p> <button onClick={logout}>Logout</button> </div> ); } ``` ## ๐ŸŽฏ Get Your API Keys 1. Visit the [AccessKit Dashboard](https://access-kit.vercel.app/) 2. Create an account or login 3. Create a new project 4. Copy your Project ID and API Key from the project settings ## ๐Ÿช useAuth Hook The `useAuth` hook provides all authentication functionality: ```typescript interface AuthContextType { // State user: User | null; isLoading: boolean; isAuthenticated: boolean; // Actions login: (email: string, password: string) => Promise<void>; register: (userData: RegisterData) => Promise<void>; logout: () => Promise<void>; updateProfile: (userData: Partial<User>) => Promise<void>; requestPasswordReset: (email: string) => Promise<void>; resetPassword: (token: string, password: string) => Promise<void>; verifyEmail: (token: string) => Promise<void>; // Direct SDK access client: AuthClient; } ``` ### Registration ```jsx function SignupForm() { const { register, isLoading } = useAuth(); const handleSubmit = async (formData) => { try { await register({ email: formData.email, password: formData.password, firstName: formData.firstName, lastName: formData.lastName, username: formData.username, customFields: { role: 'user' } }); // User is automatically logged in after registration } catch (error) { console.error('Registration failed:', error); } }; return ( <form onSubmit={handleSubmit}> {/* Your form fields */} <button type="submit" disabled={isLoading}> {isLoading ? 'Creating Account...' : 'Sign Up'} </button> </form> ); } ``` ### Profile Management ```jsx function UserProfile() { const { user, updateProfile } = useAuth(); const handleUpdate = async (newData) => { try { await updateProfile(newData); // User state is automatically updated } catch (error) { console.error('Update failed:', error); } }; return ( <div> <h2>{user.firstName} {user.lastName}</h2> <p>{user.email}</p> <button onClick={() => handleUpdate({ firstName: 'NewName' })}> Update Name </button> </div> ); } ``` ## ๐Ÿงฉ Ready-to-Use Components ### LoginForm Component A complete login form with validation: ```jsx import { LoginForm } from '@gsarthak783/accesskit-react'; function LoginPage() { return ( <div className="login-page"> <LoginForm onSuccess={() => console.log('Login successful!')} onError={(error) => console.error('Login failed:', error)} className="custom-login-form" buttonText="Sign In" showSignupLink={true} onSignupClick={() => navigate('/signup')} /> </div> ); } ``` ### LoginForm Props ```typescript interface LoginFormProps { onSuccess?: () => void; onError?: (error: Error) => void; className?: string; buttonText?: string; showSignupLink?: boolean; onSignupClick?: () => void; } ``` ## โš™๏ธ AuthProvider Configuration ```jsx <AuthProvider config={{ projectId: 'your-project-id', apiKey: 'your-api-key', baseUrl: 'https://access-kit-server.vercel.app/api/project-users', // Optional, defaults to this timeout: 10000 // Optional, request timeout in ms }} storage={customStorage} // Optional, custom token storage autoInitialize={true} // Optional, auto-check authentication on mount > <App /> </AuthProvider> ``` ### Custom Storage ```jsx import { AuthProvider } from '@gsarthak783/accesskit-react'; // Custom storage for React Native or other environments const customStorage = { getItem: (key) => AsyncStorage.getItem(key), setItem: (key, value) => AsyncStorage.setItem(key, value), removeItem: (key) => AsyncStorage.removeItem(key) }; <AuthProvider config={config} storage={customStorage}> <App /> </AuthProvider> ``` ## ๐ŸŽจ Advanced Usage ### Account Security ```jsx import { useAuth } from '@gsarthak783/accesskit-react'; function AccountSettings() { const { user, updatePassword, updateEmail, reauthenticateWithCredential } = useAuth(); const [isReauthenticated, setIsReauthenticated] = useState(false); // Change password const handlePasswordChange = async (currentPassword, newPassword) => { try { await updatePassword(currentPassword, newPassword); alert('Password updated successfully! Please login again.'); // User will be logged out automatically } catch (error) { alert(`Error: ${error.message}`); } }; // Update email const handleEmailUpdate = async (newEmail, password) => { try { const result = await updateEmail(newEmail, password); alert(`Email updated to ${result.email}. Please verify your new email.`); } catch (error) { alert(`Error: ${error.message}`); } }; // Reauthenticate before sensitive operations const handleReauthentication = async (password) => { try { const result = await reauthenticateWithCredential(password); setIsReauthenticated(true); console.log('Reauthenticated at:', result.authenticatedAt); // Now you can show sensitive settings } catch (error) { alert('Invalid password'); } }; return ( <div> {!isReauthenticated ? ( <form onSubmit={(e) => { e.preventDefault(); handleReauthentication(e.target.password.value); }}> <input type="password" name="password" placeholder="Enter password to continue" /> <button type="submit">Verify Identity</button> </form> ) : ( <div> {/* Show sensitive account settings */} </div> )} </div> ); } ``` ### Protected Routes ```jsx import { useAuth } from '@gsarthak783/accesskit-react'; import { Navigate } from 'react-router-dom'; function ProtectedRoute({ children }) { const { isAuthenticated, isLoading } = useAuth(); if (isLoading) { return <div>Loading...</div>; } return isAuthenticated ? children : <Navigate to="/login" />; } // Usage <ProtectedRoute> <Dashboard /> </ProtectedRoute> ``` ### Role-Based Access ```jsx function AdminPanel() { const { user, isAuthenticated } = useAuth(); if (!isAuthenticated || user.customFields?.role !== 'admin') { return <div>Access denied</div>; } return <div>Admin content</div>; } ``` ### Form Validation ```jsx function RegisterForm() { const { register, isLoading } = useAuth(); const [formData, setFormData] = useState({ email: '', password: '', firstName: '', lastName: '' }); const [errors, setErrors] = useState({}); const validateForm = () => { const newErrors = {}; if (!formData.email.includes('@')) { newErrors.email = 'Invalid email'; } if (formData.password.length < 6) { newErrors.password = 'Password must be at least 6 characters'; } setErrors(newErrors); return Object.keys(newErrors).length === 0; }; const handleSubmit = async (e) => { e.preventDefault(); if (!validateForm()) return; try { await register(formData); } catch (error) { setErrors({ submit: error.message }); } }; return ( <form onSubmit={handleSubmit}> <input type="email" value={formData.email} onChange={(e) => setFormData({...formData, email: e.target.value})} placeholder="Email" /> {errors.email && <span className="error">{errors.email}</span>} <input type="password" value={formData.password} onChange={(e) => setFormData({...formData, password: e.target.value})} placeholder="Password" /> {errors.password && <span className="error">{errors.password}</span>} <button type="submit" disabled={isLoading}> {isLoading ? 'Registering...' : 'Register'} </button> {errors.submit && <div className="error">{errors.submit}</div>} </form> ); } ``` ### Direct SDK Access ```jsx function AdvancedComponent() { const { client } = useAuth(); const handleAdminAction = async () => { try { // Direct access to the underlying AuthClient const users = await client.getAllUsers({ page: 1, limit: 10, status: 'active' }); console.log('Users:', users); } catch (error) { console.error('Failed to fetch users:', error); } }; return ( <button onClick={handleAdminAction}> Get All Users (Admin) </button> ); } ``` ## ๐ŸŽจ Styling The components come with minimal styling. You can customize them using CSS classes: ```css /* Custom styles for LoginForm */ .custom-login-form { max-width: 400px; margin: 0 auto; padding: 2rem; border-radius: 8px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } .custom-login-form input { width: 100%; padding: 0.75rem; margin-bottom: 1rem; border: 1px solid #ddd; border-radius: 4px; } .custom-login-form button { width: 100%; padding: 0.75rem; background-color: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; } .custom-login-form button:disabled { background-color: #6c757d; cursor: not-allowed; } ``` ## ๐Ÿ”’ Security Best Practices ### Environment Variables Never expose API keys in frontend code: ```javascript // โŒ Don't do this const config = { projectId: 'proj_123', apiKey: 'sk_live_abc123' // Never expose secret keys in frontend! }; // โœ… Do this instead const config = { projectId: process.env.REACT_APP_PROJECT_ID, // Use public keys only in frontend, manage auth server-side }; ``` ### Secure Token Storage ```jsx // For React Native or when you need secure storage import * as SecureStore from 'expo-secure-store'; const secureStorage = { getItem: async (key) => await SecureStore.getItemAsync(key), setItem: async (key, value) => await SecureStore.setItemAsync(key, value), removeItem: async (key) => await SecureStore.deleteItemAsync(key) }; <AuthProvider config={config} storage={secureStorage}> <App /> </AuthProvider> ``` ### Input Validation Always validate user inputs: ```jsx const validateEmail = (email) => { const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; return emailRegex.test(email); }; const validatePassword = (password) => { return password.length >= 8 && /[A-Z]/.test(password) && /[a-z]/.test(password) && /\d/.test(password); }; ``` ## ๐Ÿงช Examples ### Complete Login/Register Flow ```jsx import React, { useState } from 'react'; import { useAuth, LoginForm } from '@gsarthak783/accesskit-react'; function AuthFlow() { const { isAuthenticated, user, logout } = useAuth(); const [showLogin, setShowLogin] = useState(true); if (isAuthenticated) { return ( <div> <h1>Welcome, {user.firstName}!</h1> <button onClick={logout}>Logout</button> </div> ); } return ( <div> <div className="auth-tabs"> <button onClick={() => setShowLogin(true)} className={showLogin ? 'active' : ''} > Login </button> <button onClick={() => setShowLogin(false)} className={!showLogin ? 'active' : ''} > Register </button> </div> {showLogin ? ( <LoginForm onSuccess={() => console.log('Logged in!')} showSignupLink={true} onSignupClick={() => setShowLogin(false)} /> ) : ( <RegisterForm onSuccess={() => setShowLogin(true)} /> )} </div> ); } ``` ### TypeScript Support ```tsx import { useAuth } from '@gsarthak783/accesskit-react'; import type { User } from '@gsarthak783/accesskit-auth'; interface UserProfileProps { onUpdate?: (user: User) => void; } const UserProfile: React.FC<UserProfileProps> = ({ onUpdate }) => { const { user, updateProfile } = useAuth(); const handleUpdate = async (data: Partial<User>) => { try { const updatedUser = await updateProfile(data); onUpdate?.(updatedUser); } catch (error) { console.error('Update failed:', error); } }; return ( <div> <h2>{user?.firstName} {user?.lastName}</h2> <button onClick={() => handleUpdate({ firstName: 'New Name' })}> Update Profile </button> </div> ); }; ``` ## ๐Ÿ“ž Support - **Live Demo**: [https://access-kit.vercel.app/](https://access-kit.vercel.app/) - **npm Package**: [https://npmjs.com/package/@gsarthak783/accesskit-react](https://npmjs.com/package/@gsarthak783/accesskit-react) - **Core SDK**: [https://npmjs.com/package/@gsarthak783/accesskit-auth](https://npmjs.com/package/@gsarthak783/accesskit-auth) ## ๐Ÿ“„ License MIT License - see [LICENSE](./LICENSE) file for details. ## ๐Ÿ“ Changelog ### 1.2.2 (Latest) - Updated to use @gsarthak783/accesskit-auth v1.2.2 with critical token storage fix - Tokens are now properly saved during login/register ### 1.2.1 - Fixed authentication persistence across page refreshes - Improved integration with core SDK's initialization flow - Better handling of loading states during auth checks ### 1.2.0 - Added support for account security methods - Exposed `updatePassword`, `updateEmail`, and `reauthenticateWithCredential` via useAuth hook ### Version 1.1.0 - Simplified AuthProvider using onAuthStateChange from core SDK - Removed autoInitialize prop (always auto-initializes now) - Automatic auth state persistence across page refreshes - Improved TypeScript types ### Version 1.0.5 - Updated to use @gsarthak783/accesskit-auth v1.0.5 --- Built with โค๏ธ by the AccessKit Team