outofoffice/password-generator
outofoffice/password-generator provides configurable password generation for Laravel applications. Set length and character options, generate random passwords for accounts, seeds, or tests, and keep credential creation consistent through a simple, framework-friendly API.
Confirm the stack before anything else. This package is only a Laravel Nova form field. Despite the Composer description advertising a standalone PHP generator API, the shipped artifact is a field class plus a compiled Vue component. If you aren't running Nova, stop here and write ten lines of random_int()-based generation yourself.
Prerequisites to verify in a scratch branch first:
php -v # check against composer.json constraints
composer show laravel/nova # exact Nova version matters more than anything
The last tagged release is 2023-02-03, which predates Nova 5 (Dec 2023). Treat Nova 4 as the presumed-tested target and Nova 5 as unverified until you've rendered it once. Budget an hour for that spike before you commit to anything.
Install and wire it up:
composer require outofoffice/password-generator
Then in a Nova resource:
use OutOfOffice\PasswordGenerator\PasswordGenerator;
public function fields(NovaRequest $request)
{
return [
PasswordGenerator::make('Password')
->rules('required', 'min:12', 'confirmed')
->onlyOnForms(),
Password::make('Password Confirmation')
->onlyOnForms()
->creationRules('required')
->updateRules('nullable'),
];
}
Where to look first. The published source, not the README. Specifically:
src/PasswordGenerator.php — the field class; this is your actual API surface.resources/js/components/Field.vue (or components/) — the generator itself. Read this before you trust the output; see the entropy note below.dist/js/field.js — the compiled asset Nova actually loads.First use case. An internal admin screen where a support agent provisions a staff or client account and needs a strong password without inventing one. That is the entire value proposition. Do not put this on a customer-facing signup form.
Aim for this sequence on day one: get the field rendering on one resource → generate a password → save the record → confirm the value round-trips → confirm the confirmation field is populated.
The core pattern — generate on create, reveal, save. The field's job is to fill the password input. Everything downstream (hashing, validation, delivery) is your application's responsibility.
Hash before persistence, always. If your model uses a password column with a hashed cast, you're fine. If not, add a mutator:
protected function password(): Attribute
{
return Attribute::make(
set: fn (string $value) => Hash::make($value),
);
}
Never let the generated plaintext land in the database. This is the single most common way a "small convenience field" becomes a security incident.
Pair with a confirmation field. The field generates one string; if your form has a password_confirmation input, the two will diverge and the user gets a "passwords do not match" error the field itself caused. Either omit the confirmation on generated flows, or verify the field populates both (check the Vue component — this is a real behavior question, not a rhetorical one). confirmed validation is only useful if you've confirmed the population behavior.
Server-side validation is non-negotiable. The field is a UI affordance, not a policy. Whatever the field offers for length/charset, enforce the same floor and ceiling in rules:
use Illuminate\Validation\Rules\Password;
->rules(['required', Password::min(16)->letters()->numbers()->symbols()->uncompromised()])
The uncompromised() check is the one thing the field can never give you — it's a breach-list lookup, not a generator.
Scope it to create forms. Generated passwords on update forms mean an admin can silently reset a user's password by saving an unrelated field. Guard it:
PasswordGenerator::make('Password')
->onlyOnForms()
->hideFromIndex()
->fillOnCreate() // note: broken before v1.8.1
Pin >=1.8.1 — the fillOnCreate() typo was fixed in that release, and if your Composer constraint allows 1.8.0 a fresh install can resolve the broken version.
Integrate with Nova's built-in password conventions. If you're already using Laravel\Nova\Fields\Password elsewhere for hashing-on-save behavior, check whether this field delegates to it or reimplements it. If it reimplements, you may need to do the hashing in the model to keep both code paths consistent.
Fallback pattern: vendor it. MIT license, tiny surface, zero dependents, no releases in two years. Copy the field class and Vue component into app/Nova/Fields under your namespace. You get:
dist/js can be rebuilt against your own Nova/Vite version.Math.random() for crypto.getRandomValues() in five minutes if it isn't already.For a field this small, vendoring is usually cheaper than the ongoing risk of an unmaintained dependency in a credential path.
If you're not on Nova. Do not try to force this package into a Blade form or a Livewire component. Write the equivalent:
$alphabet = 'abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%';
$password = '';
for ($i = 0; $i < 20; $i++) {
$password .= $alphabet[random_int(0, strlen($alphabet) - 1)];
}
Note the omitted lookalikes (l, I, 1, O, 0) — the same readability concern the field's charset options implicitly address.
Entropy is the invisible failure mode. Very likely the password is generated client-side in the Vue component. If it uses Math.random(), the output is not cryptographically secure — and it will never fail in QA, because the string still looks random. Open the component and look for crypto.getRandomValues or window.crypto. If it isn't there, either patch it in your fork or generate server-side and pass the value down. This is the first thing to check and the easiest thing to miss.
Never persist or log the plaintext. Nova's request payload includes the generated password. Check whether your install has Nova's action/activity logging, Telescope, or any middleware capturing request bodies — those will happily record a live credential. Same for analytics on the admin panel. This is a security-review gate, not a nice-to-have.
Confirmation drift. Covered above, but the failure is easy to reproduce and hard to diagnose from a bug report ("it says my passwords don't match and I never typed one"). Test the create flow end-to-end with the confirmation field present before shipping.
Asset build failures, not PHP failures. Stale Nova fields almost always break at asset compilation, not in the class. The dist/js/field.js is pre-compiled against whatever Nova/Vue version existed in early 2023. If the field renders blank or the console shows a Vue component resolution error, that's the cause — you need to rebuild the component against your Nova version, which effectively means forking.
Validation re-render behavior. When a Nova form fails validation, the page reloads and the field re-populates. Confirm whether the generated password survives that round-trip or whether it's blanked (which may be the safer behavior, but will surprise users). Test it by submitting a deliberately invalid other field.
Version pinning. Zero dependents means zero downstream testing against real Nova versions. Use an exact constraint ("outofoffice/password-generator": "1.8.1") rather than ^1.8 so a fresh composer install can't drift onto an untested constraint combination at deploy time.
Don't treat the advertised scope as real. The blurb mentions standalone PHP, seeders, tests, and CLI workflows. Read the source; assume only the Nova field exists until you've confirmed otherwise.
Testing the field. Don't assert on the generated value — it's random. Assert properties:
$this->assertMatchesRegularExpression('/^[A-Za-z0-9!@#\$%]{16,}$/', $generated);
And if you vendor it, add a test that the generator produces distinct values across N runs. Weak PRNGs pass a single-output test trivially.
Where to extend. The field class is the extension point, not a config file — there's no published config here. Length and charset are per-field chainable options, so different resources can have different policies. If you need one global policy, that's a signal to vendor it and centralize.
Abandonment posture. With 11 stars, zero dependents, and a last release in February 2023, assume no upstream fixes. Assign an owner if you adopt it as-is. The honest framing for your team is "we vendored a small admin-panel convenience," not "we added a security dependency."
How can I help you explore Laravel packages today?