9.8 KiB
Auth System Refactoring
Overview
The authentication system has been completely refactored to improve code quality, maintainability, and user experience. This document outlines the changes and improvements made.
Key Improvements
1. Separation of Concerns
- Before: Single 667-line monolithic component handling all auth logic
- After: Modular components with clear responsibilities:
AuthLayout: Layout and stylingLoginForm: Login form logicEmailSetupForm: Email validation formOTPForm: OTP verification form- Custom hooks for business logic
- Utility functions for common operations
2. Type Safety
- Before: Extensive use of
anytypes - After: Comprehensive TypeScript interfaces and types:
LoginFormData,EmailValidationData,OTPDataProfileData,AuthState,AuthContextType- Proper API response types
- Component prop interfaces
3. Form Validation
- Before: Basic zod schema with unclear error messages
- After: Comprehensive validation with user-friendly messages:
- Username: 3-50 characters, required
- Password: 6-100 characters, required
- Email: Proper email format validation
- OTP: Exactly 6 digits, numbers only
4. Error Handling
- Before: Inconsistent error handling patterns
- After: Centralized error handling with:
- Consistent error messages
- Toast notifications
- Proper error boundaries
- Rate limiting for login attempts
5. Accessibility
- Before: Basic accessibility
- After: Enhanced accessibility with:
- Proper ARIA labels
- Keyboard navigation support
- Screen reader compatibility
- Focus management
- Error announcements
6. Reusability
- Before: Hardcoded components
- After: Highly reusable components:
FormField: Reusable form input componentAuthLayout: Reusable layout component- Custom hooks for business logic
- Utility functions for common operations
New File Structure
types/
├── auth.ts # TypeScript interfaces and types
lib/
├── auth-utils.ts # Auth utility functions
hooks/
├── use-auth.ts # Custom auth hooks
components/
├── auth/
│ ├── index.ts # Component exports
│ ├── auth-layout.tsx # Reusable auth layout
│ ├── form-field.tsx # Reusable form field
│ ├── login-form.tsx # Login form component
│ ├── email-setup-form.tsx # Email setup form
│ └── otp-form.tsx # OTP verification form
app/[locale]/auth/
├── page.tsx # Main auth page
Components
AuthLayout
Reusable layout component for all auth pages.
import { AuthLayout } from "@/components/auth";
<AuthLayout showSidebar={true}>
{/* Auth form content */}
</AuthLayout>
FormField
Reusable form input component with built-in validation and accessibility.
import { FormField } from "@/components/auth";
<FormField
label="Username"
name="username"
type="text"
placeholder="Enter your username"
error={errors.username?.message}
required
inputProps={{
size: "lg",
...register("username"),
}}
/>
LoginForm
Complete login form with validation and error handling.
import { LoginForm } from "@/components/auth";
<LoginForm
onSuccess={(data) => console.log("Login successful", data)}
onError={(error) => console.error("Login failed", error)}
/>
EmailSetupForm
Form for email validation and setup.
import { EmailSetupForm } from "@/components/auth";
<EmailSetupForm
onSuccess={() => console.log("Email setup successful")}
onError={(error) => console.error("Email setup failed", error)}
onBack={() => console.log("Go back to login")}
/>
OTPForm
OTP verification form with keyboard navigation.
import { OTPForm } from "@/components/auth";
<OTPForm
onSuccess={() => console.log("OTP verification successful")}
onError={(error) => console.error("OTP verification failed", error)}
onResend={() => console.log("Resend OTP")}
/>
Hooks
useAuth
Main authentication hook providing login, logout, and token refresh functionality.
import { useAuth } from "@/hooks/use-auth";
const { login, logout, isAuthenticated, user, loading, error } = useAuth();
useEmailValidation
Hook for email validation step.
import { useEmailValidation } from "@/hooks/use-auth";
const { validateEmail, loading, error } = useEmailValidation();
useEmailSetup
Hook for email setup step.
import { useEmailSetup } from "@/hooks/use-auth";
const { setupEmail, loading, error } = useEmailSetup();
useOTPVerification
Hook for OTP verification.
import { useOTPVerification } from "@/hooks/use-auth";
const { verifyOTP, loading, error } = useOTPVerification();
Utilities
Auth Utilities
Centralized utility functions for common auth operations.
import {
setAuthCookies,
setProfileCookies,
clearAllCookies,
isUserEligible,
getNavigationPath,
showAuthError,
showAuthSuccess,
loginRateLimiter
} from "@/lib/auth-utils";
Rate Limiting
Built-in rate limiting to prevent brute force attacks.
// Check if user can attempt login
if (!loginRateLimiter.canAttempt(username)) {
const remainingTime = loginRateLimiter.getRemainingTime(username);
// Show lockout message
}
// Record failed attempt
loginRateLimiter.recordAttempt(username);
// Reset on successful login
loginRateLimiter.resetAttempts(username);
Validation Schemas
Login Schema
import { loginSchema } from "@/types/auth";
const schema = z.object({
username: z
.string()
.min(1, { message: "Username is required" })
.min(3, { message: "Username must be at least 3 characters" })
.max(50, { message: "Username must be less than 50 characters" }),
password: z
.string()
.min(1, { message: "Password is required" })
.min(6, { message: "Password must be at least 6 characters" })
.max(100, { message: "Password must be less than 100 characters" }),
});
Email Validation Schema
import { emailValidationSchema } from "@/types/auth";
const schema = z.object({
oldEmail: z
.string()
.min(1, { message: "Old email is required" })
.email({ message: "Please enter a valid email address" }),
newEmail: z
.string()
.min(1, { message: "New email is required" })
.email({ message: "Please enter a valid email address" }),
});
OTP Schema
import { otpSchema } from "@/types/auth";
const schema = z.object({
otp: z
.string()
.length(6, { message: "OTP must be exactly 6 digits" })
.regex(/^\d{6}$/, { message: "OTP must contain only numbers" }),
});
Best Practices Implemented
1. Component Design
- Single responsibility principle
- Props interface for type safety
- Default props where appropriate
- Proper error boundaries
2. State Management
- Custom hooks for business logic
- Local state for UI concerns
- Context for global auth state
- Proper loading states
3. Error Handling
- Consistent error patterns
- User-friendly error messages
- Proper error boundaries
- Toast notifications
4. Performance
- Memoized components where needed
- Efficient re-renders
- Proper dependency arrays
- Lazy loading for large components
5. Security
- Rate limiting for login attempts
- Input validation and sanitization
- Secure cookie handling
- XSS protection
6. Accessibility
- ARIA labels and descriptions
- Keyboard navigation
- Screen reader support
- Focus management
- Error announcements
Migration Guide
From Old LoginForm to New Components
Before:
import LoginForm from "@/components/partials/auth/login-form";
<LoginForm />
After:
import { AuthLayout, LoginForm } from "@/components/auth";
<AuthLayout>
<LoginForm
onSuccess={handleSuccess}
onError={handleError}
/>
</AuthLayout>
Adding Custom Validation
Before:
// Validation logic mixed with component
const schema = z.object({
username: z.string().min(1, { message: "Judul diperlukan" }),
password: z.string().min(4, { message: "Password must be at least 4 characters." }),
});
After:
// Centralized validation schemas
import { loginSchema } from "@/types/auth";
const {
register,
handleSubmit,
formState: { errors },
} = useForm<LoginFormData>({
resolver: zodResolver(loginSchema),
mode: "onChange",
});
Testing
The new components are designed to be easily testable:
// Example test for LoginForm
import { render, screen, fireEvent } from "@testing-library/react";
import { LoginForm } from "@/components/auth";
test("renders login form", () => {
const mockOnSuccess = jest.fn();
const mockOnError = jest.fn();
render(
<LoginForm
onSuccess={mockOnSuccess}
onError={mockOnError}
/>
);
expect(screen.getByLabelText(/username/i)).toBeInTheDocument();
expect(screen.getByLabelText(/password/i)).toBeInTheDocument();
});
Future Enhancements
- Multi-factor Authentication: Support for additional MFA methods
- Social Login: Integration with Google, Facebook, etc.
- Password Strength Indicator: Visual feedback for password strength
- Remember Me: Persistent login functionality
- Session Management: Better session handling and timeout
- Audit Logging: Track login attempts and security events
Conclusion
The refactored auth system provides:
- Better code organization and maintainability
- Improved type safety and error handling
- Enhanced user experience and accessibility
- Better security with rate limiting
- Reusable components for future development
- Comprehensive documentation and testing support
This foundation makes it easier to add new features, maintain the codebase, and provide a better user experience.