Authentication System
Authentication Overview
Budgeting Pro uses a custom authentication system that provides secure access to both the Admin Panel and Company Panel. The system implements OTP (One-Time Password) authentication for enhanced security and supports different user types with panel-specific access controls.
Authentication Components
- CustomLogin: OTP-based login system with rate limiting
- CustomRegister: Comprehensive company registration workflow
- Panel Access Control: Separate admin and company user authentication
- Email Verification: Required email verification for all users
- Multi-tenant Support: Company-based user isolation
Key Security Features
- OTP Authentication: 6-digit OTP codes for secure login
- Rate Limiting: Protection against brute force attacks
- Email Verification: Mandatory email verification for account activation
- Panel Isolation: Strict separation between admin and company panels
- Session Management: Secure session handling with regeneration
Login Process
OTP-Based Login Workflow
The login system uses a two-step OTP authentication process for enhanced security:
Step 1: Email Submission
Process Flow:
- User enters their email address on the login form
- System validates the email exists in the database
- If valid, system generates a 6-digit OTP code
- OTP is sent to the user's email address
- Login form switches to OTP input mode
Email Validation:
- Email must exist in the user database
- Account must be active (not soft-deleted)
- System checks for valid user record before sending OTP
OTP Generation:
$otp = random_int(100000, 999999); // 6-digit random number
$user->forceFill([
'otp' => Hash::make($otp),
'otp_expires_at' => now()->addMinutes(10),
])->save();
Step 2: OTP Verification
Process Flow:
- User receives OTP via email (OtpMail)
- User enters the 6-digit OTP code
- System validates OTP against hashed stored value
- System checks OTP hasn't expired (10-minute window)
- If valid, user is authenticated and logged in
OTP Validation:
- OTP must match the hashed value stored in database
- OTP must not be expired (10-minute expiration)
- Failed attempts trigger validation errors
- Successful login clears OTP data from user record
Rate Limiting Protection
The system implements comprehensive rate limiting to prevent abuse:
Rate Limiting Rules:
- Limit: Maximum 2 OTP requests per user
- Time Window: Rate limit resets after timeout period
- Key Format:
resend-otp|{email} for user-specific limiting
- Enforcement: Applied to both initial OTP send and resend requests
Rate Limit Handling:
$key = 'resend-otp|'.$this->email;
if (RateLimiter::tooManyAttempts($key, 2)) {
$seconds = RateLimiter::availableIn($key);
// Show countdown and prevent further requests
}
OTP Resend Functionality
Users can request a new OTP if the original expires or is lost:
Resend Features:
- Countdown Timer: Real-time countdown using Alpine.js
- Rate Limiting: Same rate limits apply to resend requests
- Visual Feedback: Clear indication when resend is available
- New OTP Generation: Fresh OTP generated for each resend request
Resend Process:
- User clicks "Resend OTP" link after countdown expires
- System validates rate limits haven't been exceeded
- New 6-digit OTP generated and stored
- Email sent with new OTP code
- User interface updates to show resend confirmation
Login Form Interface
The login form dynamically adapts based on the authentication step:
Email Input Phase:
- Single email input field with validation
- Auto-focus for immediate user interaction
- Email format validation
- Registration link for new users
OTP Input Phase:
- Email field hidden, OTP field visible
- Numeric input validation (6-digit integer)
- Rate limiting status display with countdown
- Resend OTP option when available
Visual Feedback:
// Dynamic notification based on OTP status
$notificationClass = $this->otpResent
? 'bg-green-100 border-l-4 border-green-500 text-green-700' // Success
: 'bg-blue-100 border-l-4 border-blue-500 text-blue-700'; // Info
Post-Authentication Flow
After successful OTP verification:
Authentication Steps:
- User session is created via
auth()->login($user)
- OTP data cleared from user record
- Email verification status checked
- Session regenerated for security
- User redirected based on verification status
Email Verification Check:
- Verified Users: Redirected to dashboard via LoginResponse
- Unverified Users: Redirected to
/app/email-verification/prompt
- Verification Required: All users must verify email before full access
Company Registration Process
Registration Overview
The company registration system provides a comprehensive onboarding process for new B2B customers, collecting all necessary company information, location details, and contact information required for Shopify B2B integration.
Registration Form Structure
The registration form is organized into logical sections with collapsible interfaces:
1. Company Information Section
Purpose: Core company identification
Required Fields:
- Company Name: Primary company identifier (max 255 characters)
Business Logic:
- Company name becomes the primary identifier in Shopify
- Used for company creation in both local database and Shopify B2B
- Must be unique within the Shopify store context
2. Company Location Section
Purpose: Primary location/branch identification
Required Fields:
- Location Name: Primary location identifier (max 255 characters)
Business Logic:
- Creates the main company location in the system
- Used for initial budget allocation and user assignment
- Becomes the primary location for company operations
3. Shipping Address Section
Purpose: Company's primary shipping/delivery address
Required Fields:
- First Name, Last Name (contact person)
- Address Line 1, City, Country
- Postal/Zip Code
Optional Fields:
- Recipient, Phone Number
- Address Line 2, Province/State
Validation Rules:
// Country-specific validation rules
'shipping_phone' => new MobileNumber($get('shipping_country_code'))
'shipping_province' => new ZoneCode($get('shipping_country_code'))
'shipping_postal_code' => new PostalCode($get('shipping_country_code'))
4. Billing Address Section
Purpose: Company's billing/invoicing address
Smart Features:
- "Same as Shipping" Checkbox: Automatically copies shipping address
- Conditional Visibility: Only shows when billing differs from shipping
- Dynamic Validation: Required fields adjust based on checkbox state
Conditional Logic:
->visible(fn ($get) => ! $get('billing_same_as_shipping'))
->required(fn ($get) => ! $get('billing_same_as_shipping'))
5. Main Contact Information Section
Purpose: Primary user account creation
Required Fields:
- First Name, Last Name
- Email Address (unique validation)
Optional Fields:
- Mobile Number (country-specific validation)
Business Logic:
- Creates the primary user account for the company
- Email becomes the login identifier
- User automatically assigned CompanyAdmin role
6. Payment Terms Section
Purpose: B2B payment configuration
Features:
- Dynamic Loading: Payment terms fetched from Shopify via GraphQL
- Caching: Terms cached for 1 hour to improve performance
- Searchable Dropdown: User-friendly term selection
- Required Selection: Must choose payment terms for B2B setup
Payment Terms Integration:
// Cached Shopify payment terms
return Cache::remember('shopify_payment_terms', 3600, function () {
$getPaymentTermsAction = app(GetPaymentTermsAction::class);
return $getPaymentTermsAction->execute();
});
Registration Validation System
Comprehensive validation ensures data quality and prevents conflicts:
Email Uniqueness Validation
- Local Database: Checks User model for existing emails
- Shopify Integration: Validates email doesn't exist in Shopify B2B
- Dual Validation: Prevents conflicts in both systems
// Shopify email existence check
$checkShopifyAction = new CheckIfUserExistsViaEmailAction;
if ($checkShopifyAction->execute($data['email'])) {
throw ValidationException::withMessages([
'data.email' => __('A user with this email already exists in Shopify.'),
]);
}
Country-Specific Validation Rules
- Mobile Numbers: Validated against country-specific formats
- Postal Codes: Country-appropriate postal code validation
- Province/State: Validated against country subdivision codes
- Dynamic Rules: Validation rules change based on selected country
User Account Creation
The registration process creates a new user account with specific attributes:
User Record Creation:
$user = User::create([
'first_name' => $data['first_name'],
'last_name' => $data['last_name'],
'email' => $data['email'],
'mobile_number' => $data['mobile_number'] ?? null,
'password' => Hash::make(Str::random()), // Random password
'type' => 'company', // Company user type
'is_active' => false, // Inactive until verified
'email_verified_at' => null, // Email verification required
]);
Account Characteristics:
- Random Password: System generates random password (user sets via OTP)
- Company Type: Automatically set as UserType::Company
- Inactive Status: Account disabled until email verification
- Email Unverified: Must verify email before login access
Pending Registration System
Company data is stored in a pending state until processing:
PendingCompanyRegistration Model:
- Stores complete company information temporarily
- Links to created user account
- Processed during email verification
- Contains all address and payment information
Billing Address Logic:
// Smart billing address handling
$billing = [/* default values */];
if (! empty($data['billing_same_as_shipping'])) {
$billing = [
'billing_address_1' => $data['shipping_address_1'],
'billing_address_2' => $data['shipping_address_2'] ?? null,
// ... copy shipping to billing
];
}
Registration Completion Flow
After successful registration:
- User Creation: New user account created with company type
- Pending Data Storage: Company information stored in pending table
- Email Verification: Verification email sent automatically
- Success Notification: User notified of successful registration
- Redirect to Login: User directed to login page with instructions
OTP Authentication
OTP Security Implementation
The OTP system provides robust security through multiple layers of protection:
OTP Generation and Storage
Generation Process:
$otp = random_int(100000, 999999); // Cryptographically secure random
$user->forceFill([
'otp' => Hash::make($otp), // Bcrypt hashing for storage
'otp_expires_at' => now()->addMinutes(10), // 10-minute expiration
])->save();
Security Features:
- 6-Digit Codes: Balance between security and usability
- Cryptographically Secure: Uses
random_int() for generation
- Hashed Storage: OTP stored as bcrypt hash, never plain text
- Time-Limited: 10-minute expiration window
- Single Use: OTP cleared after successful authentication
OTP Delivery System
Email Delivery:
- Uses Laravel Mail system for reliable delivery
- Professional email template (OtpMail class)
- Delivery confirmation through mail system
- Retry logic for failed deliveries
Email Content:
- Clear subject line indicating OTP
- 6-digit code prominently displayed
- Instructions for use
- Security warnings about OTP sharing
- Expiration time clearly stated
Rate Limiting Protection
Multi-Level Protection:
$key = 'resend-otp|'.$this->email;
if (RateLimiter::tooManyAttempts($key, 2)) {
$seconds = RateLimiter::availableIn($key);
// Rate limit exceeded - show countdown
}
RateLimiter::hit($key); // Record attempt
Rate Limiting Features:
- Per-User Limits: Individual rate limits per email address
- Attempt Counting: Maximum 2 attempts per time window
- Automatic Reset: Rate limits automatically reset after timeout
- Visual Feedback: Real-time countdown showing remaining time
OTP User Interface
The OTP interface provides excellent user experience with security:
Dynamic Form Interface
Email Phase:
TextInput::make('email')
->label(__('Email'))
->email()
->required()
->autofocus()
->hidden(fn () => $this->otpSent) // Hidden after OTP sent
->dehydrated()
OTP Phase:
TextInput::make('otp')
->label(__('OTP'))
->required()
->integer()
->visible(fn () => $this->otpSent) // Visible after email verified
Interactive Countdown Timer
Alpine.js Implementation:
<div x-data="{
secondsLeft: {$seconds},
init() {
if (this.secondsLeft <= 0) return;
const timer = setInterval(() => {
this.secondsLeft--;
if (this.secondsLeft <= 0) {
clearInterval(timer);
}
}, 1000);
}
}">
Timer Features:
- Real-time Updates: Second-by-second countdown
- Automatic Cleanup: Timer stops when reaching zero
- Visual State Changes: UI updates based on timer status
- Action Enablement: Resend link appears when timer expires
Status Notifications
Dynamic Status Messages:
$notificationText = $this->otpResent
? __('OtpResentSuccess') // Green success message
: __('OtpSentNotification'); // Blue info message
Notification Features:
- Status-Aware Messages: Different messages for send vs resend
- Visual Distinction: Color-coded notification types
- Action Guidance: Clear instructions for next steps
- Accessibility: Proper ARIA labels and roles
OTP Verification Process
Verification Logic:
$inputOtp = intval($data['otp']);
$hashedOtp = $user ? $user->otp : null;
if (
! $user || // User must exist
! $hashedOtp || // OTP must be set
! Hash::check((string) $inputOtp, $hashedOtp) || // OTP must match
now()->isAfter($user->otp_expires_at) // OTP must not be expired
) {
$this->throwFailureValidationException(__('The provided OTP is invalid or has expired.'));
}
Verification Security:
- Multi-Factor Validation: Checks user existence, OTP match, and expiration
- Hash Verification: Uses secure bcrypt comparison
- Time Validation: Strict expiration enforcement
- Clear Error Messages: User-friendly validation feedback
User Types and Access Control
User Type System
Budgeting Pro implements a dual-user type system with strict panel access controls:
UserType Enum Implementation
enum UserType: string
{
case Admin = 'admin'; // System administrators
case Company = 'company'; // Company users
}
User Type Characteristics:
- Admin Users: Access to AdminPanel (/admin) only
- Company Users: Access to Company Panel (/app) only
- Strict Separation: No cross-panel access allowed
- Type-Based Features: Different features per user type
Panel Access Control
Access Validation:
public function canAccessPanel(Panel $panel): bool
{
if ($panel->getId() === 'admin') {
return $this->type === UserType::Admin;
}
if ($panel->getId() === 'app') {
return $this->type === UserType::Company;
}
return false;
}
Panel Isolation Benefits:
- Security: Prevents unauthorized cross-panel access
- User Experience: Simplified interface per user type
- Maintenance: Clear separation of concerns
- Scalability: Easy to add new panels or user types
Multi-Tenant Support
Tenant System for Company Users:
public function getTenants(Panel $panel): Collection
{
return $this->companies; // Company users can access multiple companies
}
public function canAccessTenant(Model $tenant): bool
{
return $this->companies->contains($tenant);
}
Multi-Tenancy Features:
- Company Isolation: Users can only access assigned companies
- Multiple Companies: Users can belong to multiple companies
- Context Switching: Seamless switching between company contexts
- Data Security: Complete isolation of company data
User Relationship System
Company Relationships:
public function companies()
{
return $this->belongsToMany(Company::class);
}
public function locations()
{
return $this->belongsToMany(Location::class, 'location_user')
->withPivot('shopify_role_assignment_id')
->withTimestamps();
}
Relationship Features:
- Many-to-Many Companies: Users can belong to multiple companies
- Location Assignments: Users assigned to specific locations
- Shopify Integration: Role assignments sync with Shopify B2B
- Pivot Data: Additional data stored in pivot tables
Security Features
Authentication Security Layers
The authentication system implements multiple security layers:
1. OTP-Based Authentication
- Two-Factor Security: Email + OTP provides two-factor authentication
- Time-Limited Codes: 10-minute expiration prevents replay attacks
- Single-Use Codes: OTP cleared after use prevents reuse
- Secure Generation: Cryptographically secure random number generation
2. Rate Limiting Protection
// Comprehensive rate limiting
$key = 'resend-otp|'.$this->email;
if (RateLimiter::tooManyAttempts($key, 2)) {
// Rate limit exceeded - prevent further attempts
throw ValidationException::withMessages([
'email' => __('Too many requests. Please try again in :seconds seconds.',
['seconds' => $seconds]),
]);
}
Rate Limiting Benefits:
- Brute Force Protection: Prevents automated attack attempts
- User-Specific Limits: Individual limits per user account
- Gradual Restriction: Progressive limitation of attempts
- Automatic Recovery: Limits automatically reset over time
3. Session Security
Session Management:
auth()->login($user); // Secure login
session()->regenerate(); // Session ID regeneration
Session Security Features:
- Session Regeneration: New session ID after authentication
- Secure Cookies: HTTP-only and secure cookie settings
- Session Timeout: Automatic logout after inactivity
- CSRF Protection: Built-in CSRF token validation
4. Data Validation Security
Input Validation:
- Email Validation: Strict email format validation
- OTP Validation: Numeric-only input with length validation
- SQL Injection Protection: Eloquent ORM prevents SQL injection
- XSS Protection: Automatic output escaping
Country-Specific Validation:
// Dynamic validation based on country
'mobile_number' => new MobileNumber($get('shipping_country_code'))
'postal_code' => new PostalCode($get('billing_country_code'))
'province' => new ZoneCode($get('shipping_country_code'))
Password Security
Password Handling:
- No User Passwords: Users don't set passwords initially
- Random Generation: System generates cryptographically secure random passwords
- OTP Authentication: Primary authentication through OTP system
- Hash Security: All passwords hashed using Laravel's bcrypt
Database Security
Secure Data Storage:
// OTP hashing for secure storage
'otp' => Hash::make($otp),
// Soft deletes for data retention
use SoftDeletes;
// Fillable attributes to prevent mass assignment
protected $fillable = [...];
Database Security Features:
- Hashed Sensitive Data: OTPs and passwords always hashed
- Mass Assignment Protection: Fillable attributes prevent unauthorized updates
- Soft Deletes: Data preservation without permanent deletion
- Query Filtering: Automatic tenant filtering for data isolation
Email Verification
Email Verification Requirement
All users must verify their email addresses before gaining full system access:
Verification Process Flow
- Registration: User completes registration form
- Account Creation: User account created in inactive state
- Verification Email: System sends verification email automatically
- Email Click: User clicks verification link in email
- Account Activation: Account activated and company data processed
- Login Access: User can now log in with OTP system
Verification Email System
Automatic Email Sending:
// Send email verification during registration
if ($user instanceof MustVerifyEmail && ! $user->hasVerifiedEmail()) {
$notification = new VerifyEmail;
$notification->url = Filament::getVerifyEmailUrl($user);
$user->notify($notification);
}
Email Features:
- Professional Templates: Branded email templates
- Secure Links: Cryptographically secure verification URLs
- Expiration: Time-limited verification links
- Resend Capability: Users can request new verification emails
Post-Login Verification Check
Verification Enforcement:
// Redirect unverified users to verification prompt
if ($user->email_verified_at === null) {
return $this->redirect('/app/email-verification/prompt');
}
Verification Benefits:
- Email Ownership: Confirms user owns the email address
- Account Security: Prevents unauthorized account creation
- Communication Channel: Ensures reliable communication method
- Compliance: Meets email verification compliance requirements
Verification Status Management
User Model Integration:
// User implements MustVerifyEmail interface
class User extends Authenticatable implements FilamentUser, HasTenants, MustVerifyEmail
{
protected function casts(): array
{
return [
'email_verified_at' => 'datetime', // Verification timestamp
// ...
];
}
}
Verification State Tracking:
- Timestamp Storage: Exact verification time recorded
- Status Checking: Easy verification status queries
- Interface Compliance: Implements Laravel's verification interface
- Database Integrity: Nullable timestamp for unverified users
Troubleshooting Authentication
Common Login Issues
OTP Not Received
Possible Causes:
- Email delivery delays or failures
- Spam/junk folder filtering
- Incorrect email address entry
- Mail server configuration issues
Solutions:
- Check spam/junk folders
- Wait 2-3 minutes for delivery
- Verify email address spelling
- Use "Resend OTP" feature after countdown
- Contact support if persistent
Prevention:
- Add sender to email whitelist
- Use corporate email addresses when possible
- Ensure mail server allows external emails
Rate Limit Exceeded
Cause: Too many OTP requests in short time period
Error Message: "Too many requests. Please try again in X seconds."
Solutions:
- Wait for countdown timer to complete
- Do not refresh page or make additional requests
- Use exact countdown time shown
- Contact support if limits seem incorrect
Prevention:
- Wait for OTP delivery before requesting resend
- Avoid multiple browser tabs/sessions
- Don't spam the resend button
OTP Expired or Invalid
Possible Causes:
- 10-minute expiration window exceeded
- Incorrect OTP entry (typos)
- Using old OTP after new one generated
- Copy/paste errors with extra characters
Solutions:
- Request new OTP using resend feature
- Carefully type OTP digits (avoid copy/paste)
- Use most recent OTP received
- Ensure no extra spaces or characters
Account Not Found
Error Message: Generic login failure message
Possible Causes:
- Email address not registered
- Account deleted or deactivated
- Typo in email address
- Using wrong email (multiple accounts)
Solutions:
- Verify email address spelling
- Try different email addresses you may have used
- Complete registration if not done
- Contact support for account status
Registration Issues
Email Already Exists Error
Causes:
- Email already registered in local system
- Email exists in Shopify B2B system
- Previous incomplete registration
Solutions:
- Try logging in instead of registering
- Use different email address
- Contact support to resolve duplicate accounts
- Complete pending email verification
Payment Terms Not Loading
Cause: Shopify API connection issues
Solutions:
- Refresh the registration page
- Wait a few minutes and try again
- Check internet connection
- Contact support if persistent
Address Validation Errors
Causes:
- Invalid postal codes for selected country
- Incorrect province/state codes
- Phone number format issues
Solutions:
- Verify postal code format for your country
- Use standard province/state abbreviations
- Include country code in phone numbers
- Check country selection matches address
System-Level Issues
Email Delivery Problems
Symptoms:
- No emails received for OTP or verification
- Delayed email delivery
- Emails going to spam consistently
Investigation Steps:
- Check system email configuration
- Verify SMTP server settings
- Review email service provider logs
- Test with different email providers
Rate Limiting Too Aggressive
Symptoms:
- Users frequently hit rate limits
- Short timeout periods
- Difficulty accessing during peak times
Adjustments:
- Review rate limiting configuration
- Adjust limits based on user feedback
- Consider different limits for different user types
- Monitor system usage patterns
Session Management Issues
Symptoms:
- Users frequently logged out
- Cross-panel access problems
- Session data corruption
Solutions:
- Review session configuration
- Check session storage settings
- Verify cookie security settings
- Monitor session timeout values
Getting Authentication Help
User Support Channels:
- In-app help system during login/registration
- Email support with specific error messages
- Live chat during business hours (if available)
- Knowledge base with step-by-step guides
Information to Provide:
- Email address being used
- Exact error messages
- Browser and device information
- Screenshots of error screens
- Time when issue occurred
System Administrator Tools:
- User account status checking
- Email delivery log review
- Rate limiting configuration
- OTP generation and verification logs
The authentication system in Budgeting Pro provides enterprise-level security while maintaining user-friendly experience. Understanding these processes helps ensure smooth onboarding and secure access for all users.