beyondcode/laravel-credentials
Store and manage app credentials like API keys and secrets in the database with encryption. beyondcode/laravel-credentials adds a simple management UI and a Credential model so you can securely retrieve secrets anywhere in your Laravel app.
Architecture fit. The package is deliberately thin: a published config file, one migration creating a credentials table, an Eloquent model using Laravel's encrypted cast over APP_KEY, a Credentials facade plus a global credentials() helper, a small command set (credentials:set, credentials:list), and a Blade CRUD screen mounted behind configurable middleware. It plugs into standard Laravel idioms (auto-discovered provider, publishable config, migration, middleware guard), so the shape is a clean architectural fit for any Laravel 6–8 monolith with a single database.
That is also the core TPM judgement: the package's value to us is as a reference design, not a runtime dependency. It demonstrates the pattern — encrypted-at-rest, database-backed, editable-without-a-deploy secrets — in about one to two engineering days' worth of code. The decision in front of us is "do we want this pattern and who owns it," not "do we composer require this and move on."
Integration feasibility. A proof of concept is genuinely cheap: publish, migrate, set one low-risk secret, read it via the helper, verify the UI is behind real auth middleware. Half a day for a Laravel-fluent engineer. The friction is entirely on the compatibility side — last release 2020-10-19, Laravel 8 only, archived upstream, zero dependents. On Laravel 9+ adoption means forking or --ignore-platform-reqs, and either way we inherit maintenance of a component that holds secrets.
Technical risk. Ranked by severity:
APP_KEY is a single point of catastrophic failure. Values are encrypted with the app key and there is no re-encryption command. Rotating APP_KEY destroys every stored credential permanently; losing it is data loss, not an incident with a recovery path.APP_KEY has plaintext. That is a SOC 2 / PCI / HIPAA-relevant scoping decision that should be made deliberately rather than inherited by installing a package.null (typically surfacing as a mystery 401 from a third party); terminal writes may not be visible to running PHP-FPM workers until cache clears; boot-time reads break fresh installs with Base table or view not found.APP_KEY-encrypted store after the table is populated is materially harder than designing them in.Key questions I need answered before recommending anything:
Stack fit. Good fit for a legacy Laravel 6–8 monolith, single region, single database, no near-term audit or rotation mandate. Poor fit for Laravel 9+, multi-tenant-by-design systems, or any environment where the secret store must be independently governed. Multi-tenancy is not supported — the table is flat, so prod./staging./tenant.{id}. key prefixes are the only isolation model.
Migration path. Two viable paths, and I'd pick per secret tier rather than globally:
| Path | When | Cost | Consequence |
|---|---|---|---|
| A — Vendor/fork and pin | Legacy Laravel 6–8, internal-only, low-blast-radius secrets | ~1 day setup + ongoing ownership | We become the maintainer; security review needs a documented rationale |
B — Rebuild in-house on native encrypted cast |
Anything production-critical, or Laravel 9+ | ~1–2 engineering days | We own the code outright; same pattern, current framework support |
Path B is the default recommendation. The package is small enough that rebuilding captures all the value and none of the archived-dependency risk — and it lets us design audit/rotation columns in from the start instead of retrofitting.
Compatibility. Hard-capped at Laravel 8 (itself past security end-of-life). Framework-side changes to encryption, casting, and service provider registration in 9/10/11 are not tracked upstream and will not be. Treat any fork as a frozen artifact requiring our own upgrade work.
Sequencing (this order matters — several steps are irreversible if done late):
auth, can:manage-credentials) and verify the middleware actually enforces auth in our app — before any data exists.model extension point, before populating the table.APP_KEY backup and recovery procedure and confirm APP_KEY is stored alongside DB backups of the credentials table — before the first real secret is written.config() unchanged. This "swap the source, not the call sites" pattern is what makes rollout reversible.credentials:set), and clear the package cache in the same step.Maintenance. If we fork: we own it, full stop. Named owner, pinned version, quarterly review against framework upgrades, and a standing item in the dependency-risk register. If we rebuild: one to two days of build plus ordinary first-party maintenance — cheaper over any horizon longer than a year. Either way, budget for the fact that upstream will never ship a fix.
Support. No upstream channel, no security patches, no issue triage. Support load is low in volume but high in consequence: the failure modes are the kind that require someone who has read the runbook at 2 a.m. That argues for a short written runbook (backup/recovery, rotation procedure, the four-step debug checklist: does credentials:list show the exact key casing → is cache stale → is the row actually ciphertext → is APP_KEY the same one that wrote it).
Scaling. Each uncached lookup is a database query. Resolve once per request or job into a local variable; never inside a loop or per collection item. The configurable cache TTL helps, but it introduces the most common production symptom: a write from a terminal isn't visible to running PHP-FPM workers until cache clears — so cache clearing must be a mandatory step in the deploy script. For tenant-scoped secrets in long-lived workers (Octane, queue), never hydrate tenant values into global config: they will leak across requests. And resolve credentials inside handle(), never in a job constructor, both to pick up rotations and to avoid serialising a secret into the queue payload.
Failure modes to plan for explicitly:
APP_KEY rotation — permanent loss of every credential. Requires backup discipline and a written re-entry procedure before the first write.Base table or view not found; guard with Schema::hasTable('credentials') or a console/test check.null — fail loudly at the call site (?? throw new RuntimeException(...)) so a missing key is a greppable error, not a third-party 401.credentials:list is safe (keys only), but stack traces, dd(), and config dumps are not. Add a CI grep and treat deploy logs as sensitive.Ramp-up. Low for a Laravel developer — hours, not days; the pattern is more disciplined than it is complex. The real ramp-up is operational, not technical: the team needs the runbook, the backup procedure, and the "don't call credentials() in a loop or a constructor" norms before the first secret lands. If we take the fork path, add a second audience: whoever picks up maintenance must be able to read roughly 500 lines of package code and reason about encryption and framework upgrade risk.
TPM recommendation: pursue this as a validated pattern with a one-to-two-day in-house build, gated on a blast-radius classification of the secrets involved and on a named owner. Do not adopt the archived package as a production dependency for customer-facing or payment credentials. If a managed secret store is already funded, use this evaluation as the argument to move the timeline forward rather than as a reason to build.
How can I help you explore Laravel packages today?