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:

  1. User enters their email address on the login form
  2. System validates the email exists in the database
  3. If valid, system generates a 6-digit OTP code
  4. OTP is sent to the user's email address
  5. 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:

  1. User receives OTP via email (OtpMail)
  2. User enters the 6-digit OTP code
  3. System validates OTP against hashed stored value
  4. System checks OTP hasn't expired (10-minute window)
  5. 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:

  1. User clicks "Resend OTP" link after countdown expires
  2. System validates rate limits haven't been exceeded
  3. New 6-digit OTP generated and stored
  4. Email sent with new OTP code
  5. 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:

  1. User session is created via auth()->login($user)
  2. OTP data cleared from user record
  3. Email verification status checked
  4. Session regenerated for security
  5. 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:

  1. User Creation: New user account created with company type
  2. Pending Data Storage: Company information stored in pending table
  3. Email Verification: Verification email sent automatically
  4. Success Notification: User notified of successful registration
  5. 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

  1. Registration: User completes registration form
  2. Account Creation: User account created in inactive state
  3. Verification Email: System sends verification email automatically
  4. Email Click: User clicks verification link in email
  5. Account Activation: Account activated and company data processed
  6. 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:

  1. Check spam/junk folders
  2. Wait 2-3 minutes for delivery
  3. Verify email address spelling
  4. Use "Resend OTP" feature after countdown
  5. 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:

  1. Wait for countdown timer to complete
  2. Do not refresh page or make additional requests
  3. Use exact countdown time shown
  4. 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:

  1. Request new OTP using resend feature
  2. Carefully type OTP digits (avoid copy/paste)
  3. Use most recent OTP received
  4. 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:

  1. Verify email address spelling
  2. Try different email addresses you may have used
  3. Complete registration if not done
  4. 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:

  1. Try logging in instead of registering
  2. Use different email address
  3. Contact support to resolve duplicate accounts
  4. Complete pending email verification

Payment Terms Not Loading

Cause: Shopify API connection issues

Solutions:

  1. Refresh the registration page
  2. Wait a few minutes and try again
  3. Check internet connection
  4. Contact support if persistent

Address Validation Errors

Causes:

  • Invalid postal codes for selected country
  • Incorrect province/state codes
  • Phone number format issues

Solutions:

  1. Verify postal code format for your country
  2. Use standard province/state abbreviations
  3. Include country code in phone numbers
  4. 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:

  1. Check system email configuration
  2. Verify SMTP server settings
  3. Review email service provider logs
  4. Test with different email providers

Rate Limiting Too Aggressive

Symptoms:

  • Users frequently hit rate limits
  • Short timeout periods
  • Difficulty accessing during peak times

Adjustments:

  1. Review rate limiting configuration
  2. Adjust limits based on user feedback
  3. Consider different limits for different user types
  4. Monitor system usage patterns

Session Management Issues

Symptoms:

  • Users frequently logged out
  • Cross-panel access problems
  • Session data corruption

Solutions:

  1. Review session configuration
  2. Check session storage settings
  3. Verify cookie security settings
  4. 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.