Skip to content

Password Reset

Total CMS includes a complete password reset workflow that allows users to securely reset their passwords via email.

  • Secure Token-Based Reset: Uses cryptographically secure tokens with configurable expiration
  • Email Integration: Sends password reset links via email with customizable templates
  • User Enumeration Prevention: Never reveals whether an email exists in the system
  • Single-Use Tokens: Tokens are automatically invalidated after successful password reset
  • Multi-Collection Support: Works with custom authentication collections
  • Configurable Expiry: Set how long reset tokens remain valid

Password reset settings are configured in the auth section of your Total CMS configuration:

$settings['auth'] = [
'enable' => true,
'collection' => 'auth',
// Password Reset Settings
'forgotPasswordMailerId' => '', // Optional: Custom mailer ID for password reset emails
'resetTokenExpiry' => 30, // Token expiration time in minutes (default: 30)
// Other auth settings...
'maxAttempts' => 10,
'deniedTimeout' => 7,
'persistentLoginDays' => 30,
];

Optional mailer ID for custom password reset email templates. If empty, the system uses the built-in default template.

Example:

'forgotPasswordMailerId' => 'custom-password-reset',

Number of minutes before a password reset token expires. Default is 30 minutes.

Example:

'resetTokenExpiry' => 60, // Tokens expire after 1 hour

Total CMS includes a beautiful, responsive HTML email template for password reset emails. The default template includes:

  • Professional styling with your site branding
  • Clear call-to-action button
  • Token expiration notice
  • Alternative link for copy/paste
  • Security reminder

The default template is located at:

resources/templates/email/password-reset.twig

You can create custom password reset email templates using the Mailer collection. This allows you to fully customize the email design and content.

  1. Navigate to the Mailer collection in the Total CMS admin
  2. Create a new mailer entry with a unique ID (e.g., custom-password-reset)
  3. Design your email template using the available Twig variables
  4. Set the forgotPasswordMailerId configuration to your mailer ID

When creating custom password reset email templates, the following variables are available via the data object:

VariableTypeDescription
data.namestringUser’s name from their account (may be empty)
data.emailstringEmail address receiving the reset link
data.userarrayComplete user object with all fields from the auth collection
data.resetUrlstringComplete password reset URL with token
data.expiryMinutesintegerNumber of minutes before token expires
data.collectionstringAuthentication collection name

Note: All variables must be accessed via the data object (e.g., {{ data.resetUrl }}), which is consistent with all custom mailer templates in Total CMS.

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Password Reset Request</title>
</head>
<body style="font-family: Arial, sans-serif; line-height: 1.6; color: #333;">
<div style="max-width: 600px; margin: 0 auto; padding: 20px;">
<h2>Password Reset Request</h2>
{% if data.name %}
<p>Hello {{ data.name }},</p>
{% else %}
<p>Hello,</p>
{% endif %}
<p>We received a request to reset your password for your account at {{ data.email }}.</p>
<p style="margin: 30px 0;">
<a href="{{ data.resetUrl }}"
style="background-color: #007bff; color: white; padding: 12px 24px;
text-decoration: none; border-radius: 4px; display: inline-block;">
Reset Your Password
</a>
</p>
<p>This link will expire in {{ data.expiryMinutes }} minutes for security reasons.</p>
<p>If you didn't request this password reset, you can safely ignore this email.
Your password will remain unchanged.</p>
<hr style="border: none; border-top: 1px solid #ddd; margin: 30px 0;">
<p style="font-size: 12px; color: #666;">
If the button doesn't work, copy and paste this link into your browser:<br>
<a href="{{ data.resetUrl }}">{{ data.resetUrl }}</a>
</p>
</div>
</body>
</html>

Note: The data.user variable provides access to all fields from the user’s account. For example:

  • {{ data.user.name }} - User’s name
  • {{ data.user.email }} - User’s email address
  • {{ data.user.customField }} - Any custom fields you’ve added to your auth collection
  1. User clicks “Forgot Password?” on the login page
  2. Enters their email address
  3. System generates a secure token and sends reset email
  4. User receives success message (regardless of whether email exists)
  1. User clicks the reset link in their email
  2. Token is validated for expiration and authenticity
  3. User enters new password (minimum 4 characters)
  4. Password is updated and user is redirected to login
  5. Token is invalidated (single-use)
  • Cryptographically Secure: Tokens are generated using random_bytes() (64-character hex)
  • Expiration: Configurable expiration time (default 30 minutes)
  • Single-Use: Tokens are deleted after successful password reset
  • Invalidation: New reset requests invalidate previous tokens for the same user

The system never reveals whether an email address exists in the database. Whether the email exists or not, users always see:

“If an account exists with that email, you will receive a password reset link.”

This prevents attackers from discovering valid email addresses.

Password reset tokens are stored in the cache system (APCu, Redis, Memcached, or filesystem) with automatic TTL-based expiration. Tokens are not stored in user files for security.

The password reset system uses the following routes:

RouteMethodDescription
/forgot-password[/{collection}]GETDisplay forgot password form
/forgot-password[/{collection}]POSTProcess forgot password request
/reset-password/{token}GETDisplay reset password form
/reset-password/{token}POSTProcess password reset

For custom authentication collections:

/forgot-password/clients
/reset-password/{token} (automatically uses correct collection from token)

The forgot password form supports an email query parameter to pre-fill the email field:

/forgot-password?email=user@example.com
/forgot-password/clients?email=client@company.com

This is useful for:

  • Directing users from other parts of your application
  • Pre-filling the email when you already know their address
  • Creating custom “reset password” links in user profiles

Example usage:

<a href="/forgot-password?email={{ user.email }}">Reset your password</a>

You can customize the forgot password and reset password forms using whitelabel templates:

Forgot Password:

  • whitelabel/forgot-password-above.twig - Content above the form
  • whitelabel/forgot-password-below.twig - Content below the form

Reset Password:

  • whitelabel/reset-password-above.twig - Content above the form
  • whitelabel/reset-password-below.twig - Content below the form

See the Whitelabel documentation for more information.

  1. Check SMTP Configuration: Verify your SMTP settings in config/settings.php
  2. Check Spam Folder: Password reset emails may be flagged as spam
  3. Verify Email Service: Check storage/logs/email.log for sending errors
  4. Test Email Service: Use the Mailer collection to send a test email

If users consistently report expired tokens:

  1. Increase the resetTokenExpiry value in auth configuration
  2. Check cache backend is working properly (APCu, Redis, etc.)
  3. Verify server time is correct (date_default_timezone_set)
  1. Verify the mailer ID exists in the Mailer collection
  2. Check forgotPasswordMailerId matches exactly (case-sensitive)
  3. Ensure mailer template includes all required variables
  4. Check storage/logs/email.log for template rendering errors

The PasswordResetService provides the following public methods:

createResetToken(string $email, string $collection): array

Section titled “createResetToken(string $email, string $collection): array”

Creates a password reset token for the specified email and collection.

Returns:

[
'success' => bool,
'token' => string, // Only present if success is true
'message' => string,
]

Validates a password reset token.

Returns:

[
'valid' => bool,
'email' => string, // Only present if valid is true
'collection' => string, // Only present if valid is true
'message' => string,
]

resetPassword(string $token, string $newPassword): array

Section titled “resetPassword(string $token, string $newPassword): array”

Resets a user’s password using a valid token.

Returns:

[
'success' => bool,
'message' => string,
]