Skip to content

Customer Authentication

Complete guide to customer registration, login, and session management in the Reserva platform.


Overview

The customer authentication system provides secure access control for end-users booking appointments through the platform.

Key Features:

  • Customer Registration - Create new customer accounts with verification workflows
  • Dual-Mode Login - Tenant-specific or central authentication
  • Session Management - Secure token-based sessions with device tracking
  • Account Security - Password hashing, verification codes, and logout controls
  • Multi-Channel Support - Email or phone-based authentication

Authentication Flow:

  1. Registration → Customer creates account with email/phone
  2. Verification → Email/SMS codes sent (prepared for delivery)
  3. Login → Customer authenticates with credentials
  4. Session Created → JWT tokens issued for API access
  5. Logout → Session invalidated, tokens revoked

Register New Customer

Create a new customer account with automatic verification workflow preparation.

Endpoint

POST /api/v1/customer/auth/register

Authentication: None required (public endpoint)

Request Body

{
  "email": "customer@example.com",
  "password": "SecurePass123!",
  "first_name": "John",
  "last_name": "Doe",
  "phone": "+1234567890",
  "tenant_slug": "luxury-spa",
  "marketing_consent": true
}

Parameters:

Field Type Required Description
email string Conditional Customer email (required if no phone)
password string Yes Account password (must meet security requirements)
first_name string Yes Customer first name
last_name string Yes Customer last name
phone string Conditional Phone with country code (required if no email)
tenant_slug string Yes Tenant identifier from URL or selection
marketing_consent boolean No Opt-in for marketing communications

Validation Rules:

  • At least one of email or phone must be provided
  • Email must be unique within the tenant
  • Phone must be unique within the tenant
  • Password must meet complexity requirements
  • Tenant must exist and be active

Response

{
  "customer_id": "507f1f77bcf86cd799439011",
  "email": "customer@example.com",
  "phone": "+1234567890",
  "first_name": "John",
  "last_name": "Doe",
  "tenant_id": "507f1f77bcf86cd799439010",
  "email_verification_required": true,
  "phone_verification_required": true,
  "message": "Registration successful. Please verify your email and phone number."
}

Registration Process

The registration flow follows these steps:

  1. Request Validation - Validate required fields and format
  2. Duplicate Check - Verify email/phone uniqueness within tenant
  3. Tenant Validation - Confirm tenant exists and is active
  4. Password Hashing - Securely hash password using bcrypt
  5. Code Generation - Generate verification codes:
  6. Email: 6-digit code, expires in 24 hours
  7. Phone: 6-digit code, expires in 15 minutes
  8. Account Creation - Create customer record with verification data
  9. Response - Return customer profile with verification requirements

Security Features:

  • Passwords hashed with bcrypt before storage
  • Verification codes are cryptographically secure
  • Account starts as is_active: true by default
  • Email and phone marked as unverified initially

Error Responses

Status Code Description Reason
201 Customer registered Registration successful
400 Validation error Invalid request format or missing required fields
404 Tenant not found Invalid tenant_slug provided
409 Customer already exists Email or phone already registered in tenant
422 Invalid request data Schema validation failed

Example: Email-Only Registration

curl -X POST http://localhost:8000/api/v1/customer/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "email": "jane@example.com",
    "password": "SecurePass456!",
    "first_name": "Jane",
    "last_name": "Smith",
    "tenant_slug": "wellness-center"
  }'

Response:

{
  "customer_id": "507f1f77bcf86cd799439012",
  "email": "jane@example.com",
  "phone": null,
  "first_name": "Jane",
  "last_name": "Smith",
  "tenant_id": "507f1f77bcf86cd799439010",
  "email_verification_required": true,
  "phone_verification_required": false,
  "message": "Registration successful. Please verify your email."
}

Example: Phone-Only Registration

curl -X POST http://localhost:8000/api/v1/customer/auth/register \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+6281234567890",
    "password": "SecurePass789!",
    "first_name": "Budi",
    "last_name": "Santoso",
    "tenant_slug": "jakarta-spa"
  }'

Response:

{
  "customer_id": "507f1f77bcf86cd799439013",
  "email": null,
  "phone": "+6281234567890",
  "first_name": "Budi",
  "last_name": "Santoso",
  "tenant_id": "507f1f77bcf86cd799439010",
  "email_verification_required": false,
  "phone_verification_required": true,
  "message": "Registration successful. Please verify your phone number."
}

Important Notes:

  • Verification code delivery requires external service integration (not yet implemented)
  • Customers can book appointments before verification (system limitation)
  • Email addresses are automatically converted to lowercase
  • Phone numbers must include country code (E.164 format recommended)

Customer Login

Authenticate customer with dual-mode login support (tenant-specific or central discovery).

Endpoint

POST /api/v1/customer/auth/login

Authentication: None required (public endpoint)

Request Body

Tenant-Specific Login (Recommended):

{
  "email": "customer@example.com",
  "password": "SecurePass123!",
  "tenant_slug": "luxury-spa"
}

Central Login (Auto-Discovery):

{
  "email": "customer@example.com",
  "password": "SecurePass123!"
}

Parameters:

Field Type Required Description
email string Conditional Customer email (required if no phone)
phone string Conditional Customer phone (required if no email)
password string Yes Account password
tenant_slug string No Tenant identifier for direct login

Validation Rules:

  • One of email or phone must be provided
  • Password is required for all login attempts
  • If tenant_slug provided, customer must belong to that tenant
  • If no tenant_slug, system auto-discovers customer's tenant

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600,
  "customer": {
    "id": "507f1f77bcf86cd799439011",
    "email": "customer@example.com",
    "first_name": "John",
    "last_name": "Doe",
    "phone": "+1234567890",
    "email_verified": false,
    "phone_verified": false
  },
  "tenant": {
    "id": "507f1f77bcf86cd799439010",
    "name": "Luxury Spa & Wellness",
    "slug": "luxury-spa",
    "description": "Premium spa services in downtown",
    "logo_url": "https://cdn.example.com/logos/luxury-spa.png",
    "theme_color": "#8B4789"
  },
  "outlets": [
    {
      "id": "507f1f77bcf86cd799439020",
      "name": "Downtown Branch",
      "slug": "downtown",
      "address": "123 Main Street, Downtown",
      "phone": "+1234567800",
      "business_hours": {
        "monday": {"open": "09:00", "close": "21:00"},
        "tuesday": {"open": "09:00", "close": "21:00"},
        "wednesday": {"open": "09:00", "close": "21:00"},
        "thursday": {"open": "09:00", "close": "21:00"},
        "friday": {"open": "09:00", "close": "22:00"},
        "saturday": {"open": "10:00", "close": "22:00"},
        "sunday": {"open": "10:00", "close": "20:00"}
      }
    },
    {
      "id": "507f1f77bcf86cd799439021",
      "name": "Uptown Branch",
      "slug": "uptown",
      "address": "456 Park Avenue, Uptown",
      "phone": "+1234567801",
      "business_hours": {
        "monday": {"open": "10:00", "close": "20:00"},
        "tuesday": {"open": "10:00", "close": "20:00"},
        "wednesday": {"open": "10:00", "close": "20:00"},
        "thursday": {"open": "10:00", "close": "20:00"},
        "friday": {"open": "10:00", "close": "21:00"},
        "saturday": {"open": "09:00", "close": "21:00"},
        "sunday": {"open": "09:00", "close": "19:00"}
      }
    }
  ],
  "preferred_outlet": {
    "id": "507f1f77bcf86cd799439020",
    "name": "Downtown Branch",
    "slug": "downtown",
    "address": "123 Main Street, Downtown",
    "phone": "+1234567800",
    "business_hours": {
      "monday": {"open": "09:00", "close": "21:00"}
    }
  },
  "paper_id_enabled": true
}

Login Modes

Use Case: Customer logs in directly through tenant-branded portal

Process:

  1. Validate tenant_slug exists and is active
  2. Find customer by email/phone within tenant scope
  3. Verify password matches hashed password
  4. Check account is active
  5. Create session with tenant context
  6. Generate JWT tokens with tenant_id
  7. Return full context (customer, tenant, outlets)

Benefits:

  • Faster authentication (no tenant discovery needed)
  • Clear tenant context from start
  • Better for branded customer portals
  • Prevents cross-tenant login attempts

2. Central Login (Auto-Discovery)

Use Case: Customer logs in through central portal without specifying tenant

Process:

  1. Find customer by email/phone across all tenants
  2. Verify password matches
  3. Use customer's tenant_id field directly (one customer = one tenant)
  4. Fetch full tenant information and active outlets
  5. Create session with customer's tenant context
  6. Generate JWT tokens with tenant_id
  7. Return full context (customer, tenant, outlets, preferred outlet)

Business Rule:

  • System enforces one customer per tenant relationship
  • Customer record contains tenant_id field used for auto-discovery
  • Returns same comprehensive response as tenant-specific login
  • Multiple tenant associations not supported (business constraint)

Authentication Tokens

Access Token:

  • Purpose: API authentication for protected endpoints
  • Expiration: 1 hour (3600 seconds)
  • Usage: Include in Authorization: Bearer {token} header
  • Contains: customer_id, tenant_id, session_id

Refresh Token:

  • Purpose: Renew access token without re-login
  • Expiration: 30 days
  • Usage: Submit to /auth/refresh endpoint
  • Contains: customer_id, tenant_id, session_id, type: "refresh"

Payment Integration

Paper ID Enabled:

The paper_id_enabled field indicates whether Paper ID payment integration is available for the tenant:

  • true - Paper ID payment gateway is enabled and configured
  • false - Paper ID integration is disabled
  • null - Paper ID is not configured for the tenant

Frontend Usage:

  • Show/hide online payment options in booking flow
  • Enable Paper ID payment method during checkout
  • Display appropriate payment UI based on availability
  • Fall back to other payment methods if not enabled

Session Management

When login succeeds, a session record is created:

{
  "customer_id": "507f1f77bcf86cd799439011",
  "tenant_id": "507f1f77bcf86cd799439010",
  "status": "active",
  "login_at": "2025-01-15T10:30:00Z",
  "last_activity": "2025-01-15T10:30:00Z",
  "expires_at": "2025-02-14T10:30:00Z",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T10:30:00Z"
}

Session Properties:

  • Status: active, expired, invalidated
  • Duration: 30 days from login
  • Activity Tracking: Updates on each API call
  • Device Info: Tracks login source (future enhancement)

Error Responses

Status Code Description Reason
200 Login successful Valid credentials, session created
400 Bad request Email or phone not provided
401 Invalid credentials Wrong password or customer not found
403 Account deactivated Customer account disabled
404 No tenant associations Central login found no tenants
404 Tenant not found Invalid tenant_slug
422 Invalid request data Schema validation failed

Example: Tenant-Specific Login

curl -X POST http://localhost:8000/api/v1/customer/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "password": "SecurePass123!",
    "tenant_slug": "luxury-spa"
  }'

Example: Phone-Based Login

curl -X POST http://localhost:8000/api/v1/customer/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "phone": "+6281234567890",
    "password": "SecurePass789!",
    "tenant_slug": "jakarta-spa"
  }'

Example: Central Login (Auto-Discovery)

curl -X POST http://localhost:8000/api/v1/customer/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "email": "customer@example.com",
    "password": "SecurePass123!"
  }'

Important Notes:

  • Email addresses are case-insensitive (automatically lowercased)
  • Failed login attempts are not rate-limited (TODO: implement rate limiting)
  • Session cleanup for expired sessions runs periodically
  • Preferred outlet determined by customer preferences or defaults to first outlet

Customer Logout

Securely log out customer with session invalidation and optional multi-device logout.

Endpoint

POST /api/v1/customer/auth/logout?everywhere=false

Authentication: Required (Bearer token)

Query Parameters

Parameter Type Required Default Description
everywhere boolean No false If true, logout from all devices/sessions

Request

No request body required.

Authentication token is extracted from the Authorization header:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...

Response

{
  "success": true,
  "message": "Logged out successfully"
}

Logout Process

Single-Device Logout (Default)

Flow:

  1. Extract customer_id from JWT token
  2. Invalidate current session in database
  3. Mark session status as invalidated
  4. Set invalidated_at timestamp
  5. Set invalidation_reason: "logout"
  6. Return success confirmation

Result:

  • Current session is invalidated
  • Access and refresh tokens become invalid
  • Other device sessions remain active

Current Limitation:

  • Session ID not yet extracted from JWT payload
  • Temporarily invalidates all customer sessions
  • TODO: Extract session_id for precise single-session logout

Multi-Device Logout (everywhere=true)

Flow:

  1. Extract customer_id from JWT token
  2. Find all active sessions for customer
  3. Invalidate all sessions simultaneously
  4. Mark all with invalidation_reason: "logout_all_devices"
  5. Clear device tracking data
  6. Return success confirmation

Result:

  • All customer sessions invalidated across devices
  • All issued tokens become invalid immediately
  • Customer must re-login on all devices

Session Invalidation Details

When a session is invalidated, the database record is updated:

{
  "_id": "507f1f77bcf86cd799439030",
  "customer_id": "507f1f77bcf86cd799439011",
  "tenant_id": "507f1f77bcf86cd799439010",
  "status": "invalidated",
  "login_at": "2025-01-15T10:30:00Z",
  "last_activity": "2025-01-15T14:45:00Z",
  "expires_at": "2025-02-14T10:30:00Z",
  "invalidated_at": "2025-01-15T15:00:00Z",
  "invalidation_reason": "logout",
  "created_at": "2025-01-15T10:30:00Z",
  "updated_at": "2025-01-15T15:00:00Z"
}

Invalidation Reasons:

  • logout - Single-device logout
  • logout_all_devices - Multi-device logout
  • password_changed - Password reset
  • security - Admin or security action
  • expired - Session lifetime exceeded

Token Blacklisting

Current Status: Not yet implemented

Planned Enhancement:

  • Add invalidated tokens to blacklist collection
  • Check blacklist on each authenticated request
  • Automatic cleanup of expired blacklist entries
  • Prevents token reuse after logout

Error Responses

Status Code Description Reason
200 Logout successful Session(s) invalidated successfully
401 Not authenticated Missing or invalid Bearer token

Note: Logout always returns success, even if session was already invalid

Example: Single-Device Logout

curl -X POST http://localhost:8000/api/v1/customer/auth/logout \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Response:

{
  "success": true,
  "message": "Logged out successfully"
}

Example: Multi-Device Logout

curl -X POST "http://localhost:8000/api/v1/customer/auth/logout?everywhere=true" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."

Response:

{
  "success": true,
  "message": "Logged out successfully"
}

Security Considerations

Immediate Effect:

  • Session invalidation is immediate in database
  • Existing tokens fail validation on next request
  • No grace period for logout

Best Practices:

  • Client apps should clear local token storage after logout
  • Implement logout confirmation for "everywhere" option
  • Log security events for audit trail
  • Consider implementing token blacklist for high-security needs

Future Enhancements:

  • Token blacklisting for complete security
  • Session-specific logout (requires JWT session_id extraction)
  • Device fingerprinting for suspicious login detection
  • Automatic logout on password change
  • Admin-initiated forced logout

Refresh Access Token

Generate new access token using valid refresh token without requiring re-login.

Endpoint

POST /api/v1/customer/auth/refresh

Authentication: None required (refresh token validation)

Request Body

{
  "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}

Parameters:

Field Type Required Description
refresh_token string Yes Valid refresh token received from login

Validation Rules:

  • Refresh token must be valid and not expired
  • Token must be of type "refresh" (not access token)
  • Associated customer must still be active
  • Customer account must not be deactivated

Response

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "token_type": "bearer",
  "expires_in": 3600
}

Response Fields:

Field Type Description
access_token string New JWT access token for API authentication
token_type string Always "bearer"
expires_in integer Token lifetime in seconds (3600 = 1 hour)

Token Refresh Process

Flow:

  1. Client submits refresh token from previous login
  2. Decode and validate refresh token structure
  3. Verify token signature and expiration
  4. Extract customer_id, tenant_id from token payload
  5. Verify token type is "refresh" (not "access")
  6. Check customer still exists and is active
  7. Generate new access token with same scope
  8. Return new access token with expiration

Security Features:

  • Token validation: Cryptographic signature verification
  • Type checking: Ensures refresh token (not access token) is used
  • Customer status: Verifies account is still active
  • Expiration handling: Automatic token lifecycle management
  • Session continuity: Maintains same customer/tenant context

When to Use Token Refresh

Use Cases:

  • Access token expired (after 1 hour)
  • Proactive refresh before expiration (recommended)
  • Silent authentication without user interaction
  • Mobile apps with long-running sessions

Best Practices:

  • Refresh access token 5-10 minutes before expiration
  • Store refresh token securely (encrypted storage)
  • Never expose refresh token in URLs or logs
  • Implement automatic retry on 401 errors

Error Responses

Status Code Description Reason
200 Token refreshed New access token generated
401 Invalid refresh token Expired, malformed, or wrong token type
401 Customer not found or deactivated Account no longer exists or is inactive
422 Invalid request data Schema validation failed

Example: Token Refresh

curl -X POST http://localhost:8000/api/v1/customer/auth/refresh \
  -H "Content-Type: application/json" \
  -d '{
    "refresh_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MDdmMWY3N2JjZjg2Y2Q3OTk0MzkwMTEiLCJ0ZW5hbnRfaWQiOiI1MDdmMWY3N2JjZjg2Y2Q3OTk0MzkwMTAiLCJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTczOTU4NzIwMH0.signature"
  }'

Response:

{
  "access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MDdmMWY3N2JjZjg2Y2Q3OTk0MzkwMTEiLCJ0ZW5hbnRfaWQiOiI1MDdmMWY3N2JjZjg2Y2Q3OTk0MzkwMTAiLCJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzM3MDAxNjAwfQ.new_signature",
  "token_type": "bearer",
  "expires_in": 3600
}

Important Notes:

  • Refresh tokens remain valid for 30 days from login
  • Refresh tokens are invalidated on logout
  • Refresh tokens are invalidated on password change
  • Store new access token and use for subsequent API calls
  • Original refresh token remains valid for future refreshes

Client Implementation Example

JavaScript:

async function refreshAccessToken(refreshToken) {
  try {
    const response = await fetch('http://localhost:8000/api/v1/customer/auth/refresh', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({
        refresh_token: refreshToken
      })
    });

    if (!response.ok) {
      throw new Error('Token refresh failed');
    }

    const data = await response.json();

    // Store new access token
    localStorage.setItem('access_token', data.access_token);

    return data.access_token;
  } catch (error) {
    // Redirect to login if refresh fails
    window.location.href = '/login';
  }
}

// Automatic refresh before expiration
setInterval(() => {
  const refreshToken = localStorage.getItem('refresh_token');
  if (refreshToken) {
    refreshAccessToken(refreshToken);
  }
}, 50 * 60 * 1000); // Refresh every 50 minutes (before 1-hour expiration)

Change Password

Change password for authenticated customer with current password verification.

Endpoint

POST /api/v1/customer/auth/change-password

Authentication: Required (Bearer token)

Request Body

{
  "current_password": "CurrentPass123!",
  "new_password": "NewSecurePass123!",
  "logout_other_devices": true
}

Parameters:

Field Type Required Description
current_password string Yes Current account password for verification
new_password string Yes New password (must meet security requirements)
logout_other_devices boolean No If true, invalidate all other sessions (default: false)

Validation Rules:

  • Current password must match stored password hash
  • New password must meet security requirements:
  • Minimum 8 characters (recommended)
  • Mix of letters, numbers, and symbols
  • Different from current password
  • Customer must be authenticated with valid token

Response

{
  "success": true,
  "message": "Password changed successfully"
}

Password Change Process

Flow:

  1. Extract customer info from JWT token
  2. Verify current password matches stored hash
  3. Validate new password meets security requirements
  4. Generate secure bcrypt hash for new password
  5. Update customer record with new password hash
  6. Optionally invalidate other device sessions
  7. Update timestamp for password change audit
  8. Return success confirmation

Security Features:

  • Current password verification: Prevents unauthorized changes
  • Strength validation: New password must meet complexity requirements
  • Secure hashing: bcrypt with salt for new password
  • Session management: Option to logout other devices
  • Audit tracking: Password change timestamp recorded
  • Current session preserved: User stays logged in on current device

Session Management Options

Keep Other Sessions Active (default)

{
  "current_password": "CurrentPass123!",
  "new_password": "NewSecurePass123!",
  "logout_other_devices": false
}

Result:

  • Password changed successfully
  • Current session remains active
  • Other device sessions remain active
  • All sessions continue using new password

Logout Other Devices

{
  "current_password": "CurrentPass123!",
  "new_password": "NewSecurePass123!",
  "logout_other_devices": true
}

Result:

  • Password changed successfully
  • Current session remains active
  • All other sessions invalidated
  • User must re-login on other devices with new password

Use Case: Enhanced security when password is compromised or suspected breach

Error Responses

Status Code Description Reason
200 Password changed Password updated successfully
400 Current password incorrect Wrong current password provided
401 Not authenticated Missing or invalid Bearer token
422 Invalid request data Schema validation failed or weak password

Example: Change Password (Keep Sessions)

curl -X POST http://localhost:8000/api/v1/customer/auth/change-password \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "current_password": "OldPassword123!",
    "new_password": "NewSecurePass456!",
    "logout_other_devices": false
  }'

Response:

{
  "success": true,
  "message": "Password changed successfully"
}

Example: Change Password (Logout Everywhere)

curl -X POST http://localhost:8000/api/v1/customer/auth/change-password \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -H "Content-Type: application/json" \
  -d '{
    "current_password": "OldPassword123!",
    "new_password": "SuperSecure789!",
    "logout_other_devices": true
  }'

Response:

{
  "success": true,
  "message": "Password changed successfully"
}

Best Practices

For Users:

DO:

  • Use strong, unique passwords
  • Change password immediately if compromised
  • Use logout_other_devices: true if security concern
  • Keep current password secure
  • Choose password different from previous passwords

DON'T:

  • Share current password
  • Reuse passwords across services
  • Use weak or common passwords
  • Change password unnecessarily (reduces security fatigue)

For Frontend Developers:

DO:

  • Validate password strength client-side
  • Show password requirements clearly
  • Confirm new password with re-entry field
  • Explain logout_other_devices option clearly
  • Provide visual feedback for password strength
  • Show success message after change
  • Keep user logged in on current session

DON'T:

  • Store passwords locally
  • Submit without current password verification
  • Skip password strength validation
  • Force logout on current session
  • Log passwords in console or network logs

Security Considerations

Password Security:

  • New password is hashed with bcrypt (12 rounds)
  • Current password verification prevents unauthorized changes
  • Password change is logged for audit trail
  • Old password hash is permanently replaced

Session Security:

  • Current session always remains active
  • Other sessions optionally invalidated for security
  • Session invalidation is immediate in database
  • Tokens become invalid on next API call

Future Enhancements:

  • Password history tracking (prevent reuse of recent passwords)
  • Password strength meter integration
  • Two-factor authentication requirement for password change
  • Email notification on password change
  • Temporary account lock after failed attempts
  • Password expiration policies (enterprise feature)

Important Notes:

  • Current session is preserved even with logout_other_devices: true
  • All sessions are invalidated on password reset (forgot password flow)
  • Password changes are permanent and cannot be undone
  • Consider implementing password confirmation field in frontend
  • TODO: Extract session_id from JWT for precise session management

Security Features

Password Security

Hashing:

  • Algorithm: bcrypt with salt
  • Cost factor: 12 rounds (configurable)
  • Automatic salting for each password
  • Secure comparison to prevent timing attacks

Requirements:

  • Minimum length: 8 characters (recommended)
  • Complexity: Mix of letters, numbers, symbols
  • No common passwords (not yet enforced)
  • Password history tracking (not yet implemented)

Verification Codes

Email Verification:

  • Length: 6 characters (alphanumeric)
  • Expiration: 24 hours
  • Generation: Cryptographically secure random
  • Delivery: Email (requires external integration)

Phone Verification:

  • Length: 6 digits (numeric)
  • Expiration: 15 minutes
  • Generation: Cryptographically secure random
  • Delivery: SMS (requires external integration)

Session Security

Token Security:

  • JWT with HMAC-SHA256 signature
  • Tokens include expiration timestamps
  • Refresh tokens for long-term access
  • Session tracking in database

Session Limits:

  • Access token: 1 hour lifetime
  • Refresh token: 30 days lifetime
  • Session: 30 days from login
  • Automatic cleanup of expired sessions

Privacy Protection

Anti-Enumeration:

  • Forgot password returns same message for any email
  • No indication whether account exists
  • Consistent response times to prevent timing attacks

Data Protection:

  • Passwords never stored in plain text
  • Verification codes cleared after use
  • Sensitive fields excluded from logs
  • GDPR compliance considerations

Integration with Other Systems

Subscription Plan Limitations

Customer Registration:

  • Each customer registration counts toward subscription limits
  • FREE plan: Limited customers per month (check plan limits)
  • PRO/ENTERPRISE: Higher or unlimited customer registrations

Reference: See Subscription Management for plan details

Appointment Booking

After Login:

  • Customer can browse services at outlets
  • View availability grid for staff/services
  • Book appointments (requires valid session)
  • Receive booking confirmations

Reference: See Appointment Management

Customer Profile

Profile Management:

  • Update personal information
  • Set preferred outlet
  • Manage notification preferences
  • View booking history
  • Track loyalty points

Reference: See Customer Management


Best Practices

For Frontend Developers

Registration Flow:

DO:

  • Validate password strength client-side
  • Show password requirements clearly
  • Provide feedback for duplicate email/phone
  • Explain verification process
  • Support both email and phone registration
  • Validate phone number format (E.164)

DON'T:

  • Store passwords locally
  • Skip email/phone validation
  • Allow weak passwords
  • Submit without tenant_slug

Login Flow:

DO:

  • Store tokens securely (HttpOnly cookies or secure storage)
  • Implement automatic token refresh
  • Handle 401 errors with re-login prompt
  • Clear tokens on logout
  • Use tenant-specific login when possible
  • Show loading states during authentication

DON'T:

  • Store tokens in localStorage (XSS risk)
  • Ignore token expiration
  • Hard-code tenant_slug
  • Expose refresh tokens to JavaScript

Session Management:

DO:

  • Refresh access token before expiration
  • Implement activity timeout on frontend
  • Provide "logout everywhere" option in settings
  • Clear all local data on logout
  • Show active session list to user

DON'T:

  • Continue using expired tokens
  • Ignore 401 responses
  • Store sensitive data in browser storage

For Backend Developers

Security:

DO:

  • Always hash passwords with bcrypt
  • Validate tenant_slug exists
  • Check account status before login
  • Invalidate sessions on security events
  • Log authentication attempts
  • Implement rate limiting for login attempts

DON'T:

  • Store plain-text passwords
  • Trust client-provided tenant_id
  • Skip password verification
  • Expose user enumeration
  • Allow unlimited login attempts

Performance:

DO:

  • Index customers by email and phone
  • Cache tenant lookups
  • Use database connection pooling
  • Implement session cleanup job
  • Paginate session history queries

DON'T:

  • Query without indexes
  • Create new DB connection per request
  • Leave expired sessions in database
  • Return full customer history in login response

Troubleshooting

Registration Issues

Problem: "Customer already exists" error

Causes:

  1. Email already registered in tenant
  2. Phone number already registered in tenant
  3. Case-insensitive email match

Solution:

  • Check if customer exists with GET /api/v1/customer/profile
  • Use forgot-password flow if customer forgot credentials
  • Try different email or phone number

Problem: "Tenant not found" error

Causes:

  1. Invalid tenant_slug
  2. Tenant is soft-deleted
  3. Tenant is inactive

Solution:

  • Verify tenant_slug spelling
  • Check tenant is active in database
  • Use correct tenant_slug from URL or selection

Login Issues

Problem: "Invalid credentials" error

Causes:

  1. Wrong password
  2. Email not matching (case-sensitive on input)
  3. Customer doesn't exist in specified tenant
  4. Account deactivated

Solution:

  • Verify email/phone and password
  • Try forgot-password flow
  • Check tenant_slug is correct
  • Contact support if account deactivated

Problem: "Access denied for this tenant" error

Causes:

  1. Customer belongs to different tenant
  2. Attempting cross-tenant login

Solution:

  • Verify correct tenant_slug
  • Use central login (without tenant_slug) to auto-discover
  • Customer may need to register with this tenant

Problem: 500 Internal Server Error on central login

Causes:

  1. ~~Missing get_customer_tenants() method (fixed in v1.2.0)~~
  2. Database connection issues
  3. Invalid customer record structure

Solution:

  • Ensure customer record has valid tenant_id field
  • Check database connectivity
  • Verify tenant exists and is not soft-deleted
  • Review server logs for detailed error messages

Note: Central login now uses customer's tenant_id field directly instead of querying tenant associations

Session Issues

Problem: Token expired errors

Causes:

  1. Access token expired (1 hour limit)
  2. Session expired (30 days limit)
  3. Session invalidated by logout

Solution:

  • Use refresh token to get new access token
  • Re-login if refresh token expired
  • Check session wasn't invalidated by admin

Problem: Logout not working

Causes:

  1. Invalid Bearer token in header
  2. Session already invalidated
  3. Token blacklist not checked (if enabled)

Solution:

  • Verify token format in Authorization header
  • Check token hasn't expired
  • Review server logs for detailed error

API Reference Summary

Endpoint Method Auth Purpose Key Response
/customer/auth/register POST None Create customer account Customer ID, verification requirements
/customer/auth/login POST None Authenticate customer JWT tokens, tenant context, outlets
/customer/auth/logout POST Bearer Invalidate session(s) Success confirmation
/customer/auth/refresh POST None Refresh access token New access token
/customer/auth/change-password POST Bearer Change account password Success confirmation

Next Steps

After implementing authentication:

  1. Integrate verification: Set up email/SMS service for code delivery
  2. Implement refresh: Add token refresh endpoint usage
  3. Build profile: Allow customers to update their information
  4. Enable booking: Let authenticated customers book appointments
  5. Track activity: Monitor customer engagement and sessions

Related Documentation: