Generating Secure Initial Passwords in Laravel with Str::password()
Scott Keck-Warren • July 3, 2026
A client came to me with an interesting constraint. They were building an app where some users didn't have email addresses. That's not unusual in certain industries where you're onboarding workers who might share a company device or just don't have a personal email on file.
The problem is that the standard Laravel approach to initial passwords leans hard on email. You create the account, send a password reset link, and the user sets their own password. Without email, that whole chain breaks, and they needed a real, unguessable password handed to the user (on a piece of paper or texted).
My first instinct was to reach for something like Str::random() and call it a day. That works, but Laravel ships with something purpose-built for this exact situation: Str::password().
What Str::password() Does
Str::password() generates a cryptographically random password string. It uses PHP's random_int() under the hood, so the output is actually unpredictable, not just pseudo-random.
The basic call:
use Illuminate\Support\Str;
$password = Str::password();
That gives you a 32-character string mixing letters, numbers, and symbols. Something like 7Kv!3pNz#mQ8@rTw2Yx$bLd5Js&hFq1. It's not something a user is going to type correctly on the first try, but it's not in any rainbow tables or dictionary.
The Parameters
The method signature gives you control over what goes into the password:
Str::password(
length: 32, // How long the password should be
letters: true, // Include uppercase and lowercase letters
numbers: true, // Include digits 0-9
symbols: true, // Include special characters like !@#$%
spaces: false // Include spaces (off by default)
);
All five parameters have defaults, so you only need to pass what you want to change. If you need a shorter password for a PIN-style system, you can set the length. If you're working with a legacy system that chokes on special characters, you can turn off symbols.
// 16-character password, no symbols
$password = Str::password(length: 16, symbols: false);
// 12-character numeric-only (for a PIN or access code)
$password = Str::password(length: 12, letters: false, symbols: false);
I'd leave symbols on whenever the target system supports them. More character types means a larger effective keyspace, which means harder to brute force.
Putting It Together in a Service
I put the logic in a UserService class so it's easy to reuse and test.
<?php
namespace App\Services;
use App\Models\User;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
class UserService
{
public function createUserWithInitialPassword(array $data): array
{
$plainPassword = Str::password(length: 16, symbols: false);
$user = User::create([
'name' => $data['name'],
'email' => $data['email'] ?? null,
'password' => Hash::make($plainPassword),
]);
return [
'user' => $user,
'password' => $plainPassword,
];
}
}
The method returns both the user and the plain-text password intentionally. You need the plain-text version exactly once: to show it to whoever is handing the credentials to the new user. After that, you never see it again, and Laravel only stores the hash in the database.
The controller side is straightforward:
public function store(StoreUserRequest $request): JsonResponse
{
$result = $this->userService->createUserWithInitialPassword(
$request->validated()
);
return response()->json([
'message' => 'User created successfully.',
'initial_password' => $result['password'],
]);
}
For my client's use case, the admin UI displayed the password in a modal right after account creation. The admin wrote it down (or printed it) and handed it to the user in person. Once the modal closed, it was gone forever, and they had to reset it (using the same logic).
When This Pattern Makes Sense
This isn't the right approach for every app. If your users have email addresses, the standard "send a reset link" flow is better because the user sets their own password, and you never have to handle plain text at all.
But there are real situations where email isn't available or isn't practical:
- Internal tools where employees are provisioned by IT
- Apps for workers in the field who share devices or don't have personal email
- Kiosk or POS accounts where someone needs credentials immediately
Str::password() covers all of those without any reinvention on your part.
The one rule to carry into any implementation: only show the plain-text password once. Don't log it, store it in a separate column, or email it anywhere (which would be pretty ironic given the whole premise). Generate it, hash it, display it once, and you're done.