DEV Community

A0mineTV
A0mineTV

Posted on

Enhanced Validation in Laravel 12: Introducing secureValidate()

Laravel 12 brings a new, streamlined approach to enforcing strong validation rules—especially for sensitive fields like passwords—through the secureValidate() method. In this article, we’ll cover:

  1. Why “enhanced validation” matters
  2. The difference between validate() and secureValidate()
  3. How to configure your password policy with Password::defaults()
  4. Examples using secureValidate() in controllers and Form Requests
  5. Customizing validation rules beyond the defaults

1. Why “Enhanced Validation” Matters

In many applications, user-supplied data must meet strict security and formatting requirements. For instance, password fields often require:

  • Minimum length (e.g. 8+ characters)
  • At least one uppercase letter
  • At least one lowercase letter
  • At least one digit
  • At least one symbol
  • Not appearing in known data breaches (uncompromise)

Prior to Laravel 12, developers had to manually stack multiple rules—for example, multiple regex rules—to enforce complexity. This approach can be error-prone, difficult to maintain, and easy to forget in different parts of the codebase.

Laravel 12 introduces a concise way to declare “strong” validation via the new secureValidate() method. When you use the strong rule, Laravel applies a centralized password policy (configurable in one place). This leads to:

  • Better maintainability: Change your policy once and it applies everywhere the strong rule is used.
  • Cleaner controllers: No more copy-pasting complex regex chains.
  • Built-in breach checks: Automatically ensures that passwords are not compromised in public data leaks.

2. validate() vs. secureValidate()

In Laravel 11 and earlier, you would write something like:

// Before Laravel 12
public function register(Request $request)
{
    $data = $request->validate([
        'name'     => ['required', 'string', 'max:255'],
        'email'    => ['required', 'email', 'max:255', 'unique:users,email'],
        'password' => [
            'required',
            'string',
            'min:8',
            'regex:/[a-z]/',      // at least one lowercase letter
            'regex:/[A-Z]/',      // at least one uppercase letter
            'regex:/[0-9]/',      // at least one digit
            'regex:/[@$!%*#?&]/', // at least one symbol
            'confirmed'
        ],
    ]);

    User::create([
        'name'     => $data['name'],
        'email'    => $data['email'],
        'password' => Hash::make($data['password']),
    ]);

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Notice how verbose the password rules are: multiple regex patterns and a minimum length. If you have multiple forms (e.g., registration, password reset, profile update), you’d duplicate those rules everywhere.

Enter secureValidate()

With Laravel 12, you can replace that entire block with:

// Laravel 12+
public function register(Request $request)
{
    $data = $request->secureValidate([
        'name'     => ['required', 'string', 'max:255'],
        'email'    => ['required', 'email', 'max:255', 'unique:users,email'],
        'password' => ['required', 'string', 'strong', 'confirmed'],
    ]);

    User::create([
        'name'     => $data['name'],
        'email'    => $data['email'],
        'password' => Hash::make($data['password']),
    ]);

    // ...
}
Enter fullscreen mode Exit fullscreen mode

Key points:

  • secureValidate() is functionally identical to validate(), except that it recognizes the special strong rule.
  • When you declare 'strong', Laravel will automatically enforce a predefined set of password constraints (length, uppercase, lowercase, digit, symbol, uncompromised).

3. Configuring the Password Policy with Password::defaults()

By default, Laravel 12 comes with a sensible password policy, but you can customize it in your App\Providers\AppServiceProvider (or any custom service provider):

<?php

namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Illuminate\Validation\Rules\Password;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Password::defaults(function () {
            return Password::min(8)          // Minimum length = 8
                          ->mixedCase()      // At least one uppercase and one lowercase
                          ->numbers()        // At least one digit
                          ->symbols()        // At least one symbol (@$!%*#?&)
                          ->uncompromised(); // Check against breach database
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

In this closure, you have several helper methods:

  • min(int $length): set minimum length.
  • mixedCase(): enforce at least one uppercase and one lowercase letter.
  • numbers(): require at least one numeric character.
  • symbols(string $list = null): require at least one symbol (default set is @$!%*#?&). You can supply your own string of allowed symbols.
  • uncompromised(): check against Have I Been Pwned to ensure the password has not appeared in known data breaches.

You can chain or omit any of these methods to suit your project’s security requirements. For example, if you want a longer minimum length of 12 and allow only a custom set of symbols, you could write:

Password::defaults(function () {
    return Password::min(12)
                  ->mixedCase()
                  ->numbers()
                  ->symbols('!$%*#?')
                  ->uncompromised();
});
Enter fullscreen mode Exit fullscreen mode

Once this is set, all validations that include the strong rule (whether via secureValidate() or a Form Request) will inherit this policy.


4. Example: Using secureValidate() in a Controller

Below is a full example of a registration controller using secureValidate():

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Hash;

class RegisterController extends Controller
{
    public function showRegistrationForm()
    {
        return view('auth.register');
    }

    public function register(Request $request)
    {
        // 1. Run enhanced validation
        $data = $request->secureValidate([
            'name'     => ['required', 'string', 'max:255'],
            'email'    => ['required', 'email', 'max:255', 'unique:users,email'],
            'password' => ['required', 'string', 'strong', 'confirmed'],
        ]);

        // 2. Create and hash the password
        $user = User::create([
            'name'     => $data['name'],
            'email'    => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        // 3. Log the user in (optional) and redirect
        auth()->login($user);
        return redirect()->route('dashboard');
    }
}
Enter fullscreen mode Exit fullscreen mode

Then in your Blade template (resources/views/auth/register.blade.php), you have:

<form method="POST" action="{{ route('register') }}">
  @csrf

  <div>
    <label for="name">Name</label>
    <input id="name" name="name" type="text" value="{{ old('name') }}" required autofocus>
    @error('name')
      <span class="error">{{ $message }}</span>
    @enderror
  </div>

  <div>
    <label for="email">Email Address</label>
    <input id="email" name="email" type="email" value="{{ old('email') }}" required>
    @error('email')
      <span class="error">{{ $message }}</span>
    @enderror
  </div>

  <div>
    <label for="password">Password</label>
    <input id="password" name="password" type="password" required>
    @error('password')
      <span class="error">{{ $message }}</span>
    @enderror
  </div>

  <div>
    <label for="password_confirmation">Confirm Password</label>
    <input id="password_confirmation" name="password_confirmation" type="password" required>
  </div>

  <button type="submit">Register</button>
</form>
Enter fullscreen mode Exit fullscreen mode

Laravel will automatically display validation errors for all fields. If the password fails the “strong” policy, you’ll see a default error message like:

The password must be at least 8 characters and contain at least one uppercase letter, one lowercase letter, one number, one symbol, and must not have appeared in a data breach.

You can customize that message in resources/lang/en/validation.php by overriding the password.strong key:

'password' => [
    'strong' => 'Your password must be at least :min characters, contain upper- and lower-case letters, a number, a symbol, and must not appear in data leaks.',
],
Enter fullscreen mode Exit fullscreen mode

5. Using secureValidate() in a Form Request

If you prefer Form Requests for cleaner controllers, the process is almost identical:

1- Generate a Form Request:

php artisan make:request RegisterRequest
Enter fullscreen mode Exit fullscreen mode

2- In app/Http/Requests/RegisterRequest.php, define:

<?php

namespace App\Http\Requests;

use Illuminate\Foundation\Http\FormRequest;

class RegisterRequest extends FormRequest
{
    public function authorize(): bool
    {
        return true; // adjust as needed
    }

    public function rules(): array
    {
        return [
            'name'     => ['required', 'string', 'max:255'],
            'email'    => ['required', 'email', 'max:255', 'unique:users,email'],
            'password' => ['required', 'string', 'strong', 'confirmed'],
        ];
    }

    public function messages(): array
    {
        return [
            'password.strong' => 'Password must be at least :min chars, include uppercase, lowercase, number, symbol, and not be compromised.',
        ];
    }
}
Enter fullscreen mode Exit fullscreen mode

3- Inject this Form Request into your controller action:

<?php

namespace App\Http\Controllers\Auth;

use App\Http\Controllers\Controller;
use App\Http\Requests\RegisterRequest;
use App\Models\User;
use Illuminate\Support\Facades\Hash;

class RegisterController extends Controller
{
    public function register(RegisterRequest $request)
    {
        // Rules from RegisterRequest include 'strong'
        $data = $request->secureValidate();

        User::create([
            'name'     => $data['name'],
            'email'    => $data['email'],
            'password' => Hash::make($data['password']),
        ]);

        auth()->loginUsingId($user->id);
        return redirect()->route('dashboard');
    }
}
Enter fullscreen mode Exit fullscreen mode

Because the rules are defined in RegisterRequest::rules(), calling $request->secureValidate() without arguments pulls those rules automatically.


6. Customizing Beyond the Defaults

6.1. Changing Minimum Length or Symbol Set

If you need a longer minimum length (e.g., 12) or a custom allowed-symbol set, adjust your provider:

// Inside AppServiceProvider@boot()
Password::defaults(function () {
    return Password::min(12)
                  ->mixedCase()
                  ->numbers()
                  ->symbols('!$@*#?')
                  ->uncompromised();
});
Enter fullscreen mode Exit fullscreen mode

Now, any validation with strong enforces at least 12 characters and requires one of !$@*#?.

6.2. Disabling the Breach Check

If you don’t want to check against compromised-password lists (e.g., for performance or privacy reasons), simply omit ->uncompromised():

Password::defaults(function () {
    return Password::min(8)
                  ->mixedCase()
                  ->numbers()
                  ->symbols();
    // No uncompromised() here
});
Enter fullscreen mode Exit fullscreen mode

 6.3. Adding Custom Regex or Additional Rules

If you require a rule that isn’t covered by strong (e.g., no whitespace), you can combine strong with any additional rule:

$data = $request->secureValidate([
    'password' => ['required', 'string', 'strong', 'regex:/^\S+$/', 'confirmed'],
]);
Enter fullscreen mode Exit fullscreen mode

This example ensures no whitespace (regex:/^\S+$/) in addition to the default “strong” policy.


7. Summary

  • Laravel 12’s secureValidate() replaces manual, error-prone password rules with a single strong rule.
  • Centralizing password constraints in Password::defaults() makes it easy to update your policy in one place.
  • By default, the strong rule enforces length, mixed-case, numbers, symbols, and breach checks—out of the box.
  • You can override or extend the policy using the helper methods on the Password class or by appending standard validation rules (e.g., custom regex).

With secureValidate(), your controllers and Form Requests become concise, maintainable, and secure by default. Give it a try in your next Laravel 12 project!

Top comments (3)

Collapse
 
yaddly profile image
Admire Mhlaba • Edited

Thank you @blamsa0mine for your wonderful and well-thought through and well-written article. May I ask if manual Controller validation is necessary when you're using a FormRequest?

// Rules from RegisterRequest include 'strong'
$data = $request->secureValidate();

The FormRequest's rules() method is automatically invoked to apply the defined rules on the submitted form fields. Kindly correct me if my line of thinking is wrong.

Collapse
 
xwero profile image
david duymelinck • Edited

You are right.

To be clear there is no secureValidate method.

The only thing real thing in the post is Password::defaults.

I didn't want to comment on this post, because I already commented on another post of the same author where another non existing method was shown. But I didn't want you to be confused.

Collapse
 
yaddly profile image
Admire Mhlaba

Thank you @xwero for your response to my comment. I appreciate you setting the record straight on this issue as it creates confusion if left unresolved.