PHPackages                             smithingdev/laravel-vault - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Security](/categories/security)
4. /
5. smithingdev/laravel-vault

ActiveLibrary[Security](/categories/security)

smithingdev/laravel-vault
=========================

A small, focused Laravel client for HashiCorp Vault: AppRole/token auth and KV2 secret CRUD.

v0.1.0(1mo ago)082↓50%MITPHPPHP ^8.2CI passing

Since Jun 16Pushed 1mo agoCompare

[ Source](https://github.com/smithingdev/laravel-vault)[ Packagist](https://packagist.org/packages/smithingdev/laravel-vault)[ RSS](/packages/smithingdev-laravel-vault/feed)WikiDiscussions main Synced 2w ago

READMEChangelog (1)Dependencies (5)Versions (2)Used By (0)

smithingdev/laravel-vault
=========================

[](#smithingdevlaravel-vault)

[![Latest Version on Packagist](https://camo.githubusercontent.com/a51817c6214f124b4f4d572ee65301fb6fc61d861de93f7e54e3dba7e51a8448/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f736d697468696e676465762f6c61726176656c2d7661756c742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/smithingdev/laravel-vault)[![Tests](https://camo.githubusercontent.com/1c4fe1ec7abec46a16b407ef55c493c6eb8db0e5b37e1905f877bb3bd84adc02/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f736d697468696e676465762f6c61726176656c2d7661756c742f74657374732e796d6c3f6272616e63683d6d61696e266c6162656c3d7465737473267374796c653d666c61742d737175617265)](https://github.com/smithingdev/laravel-vault/actions/workflows/tests.yml)[![Total Downloads](https://camo.githubusercontent.com/3e8505b4440bdfca58038e8e31e87ebc0184ca397708cde06a5f177b1dd52083/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f736d697468696e676465762f6c61726176656c2d7661756c742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/smithingdev/laravel-vault)[![PHP Version](https://camo.githubusercontent.com/785f6938877ae189a93294a89d88848bef872fe40bf7c42c6b0f1f124c604ab0/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f736d697468696e676465762f6c61726176656c2d7661756c742e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/smithingdev/laravel-vault)[![License](https://camo.githubusercontent.com/7ceb0f117824481f045806ebae96a846b0de7f8a468a186ef5aa8239ae5ac0f5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f736d697468696e676465762f6c61726176656c2d7661756c742e7376673f7374796c653d666c61742d737175617265)](LICENSE)

A small, focused Laravel client for [HashiCorp Vault](https://www.vaultproject.io/): AppRole/token authentication and KV2 secret CRUD. No domain assumptions — just the transport.

Requirements
------------

[](#requirements)

- PHP 8.2+
- Laravel 11, 12, or 13 (`illuminate/http` + `illuminate/support`)

Install
-------

[](#install)

```
composer require smithingdev/laravel-vault
php artisan vendor:publish --tag=vault-config
```

The service provider and `Vault` facade are auto-discovered.

Configure
---------

[](#configure)

Set these in `.env`:

```
VAULT_ADDR=https://vault.example.com
VAULT_AUTH_METHOD=approle          # approle | token

# AppRole
VAULT_ROLE_ID=...
VAULT_SECRET_ID=...

# or a pre-issued token
VAULT_TOKEN=hvs....

# Namespaces (see "Namespace modes" below)
VAULT_NAMESPACE_MODE=header        # header (default) | path
VAULT_NAMESPACE=acme/dev           # Enterprise namespace (optional)
VAULT_KV_MOUNT=secret              # KV2 mount point (header mode)

VAULT_VERIFY=true                  # TLS verification (see below)
VAULT_TIMEOUT=10                   # per-request timeout in seconds (default 10)
VAULT_LOG_CHANNEL=                 # optional log channel (see below)
```

### TLS verification

[](#tls-verification)

`VAULT_VERIFY` controls how the Vault server's certificate is checked:

- `true` (default) — verify against the system CA bundle. Keep this in production.
- `false` — disable verification. Only for local/self-signed setups you trust.
- a filesystem path — verify against a custom CA bundle (e.g. a private CA).

### Logging

[](#logging)

`VAULT_LOG_CHANNEL` is optional and disabled by default. When set to a configured log channel, the package logs a line on successful authentication and logs the raw Vault response body (at `error` level) whenever a request fails — the body is logged rather than thrown, so it never leaks into exception messages (see [Errors](#errors)).

### Namespace modes

[](#namespace-modes)

Vault supports two equivalent ways to address an Enterprise namespace, and this package lets you pick via `VAULT_NAMESPACE_MODE`:

- **`header` (default, conventional)** — the model the Vault CLI and official clients use. `VAULT_NAMESPACE` is sent as the `X-Vault-Namespace` header on every request, and `VAULT_KV_MOUNT` is the KV2 mount point. Requests go to `/v1//data/`. If you don't use Enterprise namespaces, leave `VAULT_NAMESPACE` empty and set `VAULT_KV_MOUNT` to your mount (default `secret`).
- **`path` (legacy)** — `VAULT_KV_NAMESPACE` is the full path where the KV2 engine is mounted and is embedded directly in every KV2 URI (`/v1//data/`); the namespace header is sent on login only.

    ```
    VAULT_NAMESPACE_MODE=path
    VAULT_NAMESPACE=acme/dev                # X-Vault-Namespace on login
    VAULT_KV_NAMESPACE=acme/dev/kv-secrets  # baked into every KV2 URI
    ```

> **Upgrading from a version without `VAULT_NAMESPACE_MODE`:** the default is now `header`. If you previously relied on `VAULT_KV_NAMESPACE`, set `VAULT_NAMESPACE_MODE=path` (and keep `VAULT_KV_NAMESPACE`) to retain the old behavior.

Usage
-----

[](#usage)

Use the facade, or inject `VaultService` anywhere the container resolves dependencies:

```
use Smithingdev\Vault\Facades\Vault;

Vault::read('myapp/database');
```

```
use Smithingdev\Vault\VaultService;

class RotateSecrets
{
    public function __construct(private VaultService $vault) {}

    public function handle(): void
    {
        $this->vault->read('myapp/database');
    }
}
```

### KV2 operations

[](#kv2-operations)

```
Vault::write('myapp/database', ['username' => 'app', 'password' => '...']);

$secrets = Vault::read('myapp/database');              // array|null
$old     = Vault::read('myapp/database', version: 2);

$keys = Vault::list('myapp');                          // array

Vault::delete('myapp/database');                 // soft-delete latest version (recoverable)
Vault::destroy('myapp/database');                // permanent: all versions + metadata
```

Return values and not-found behavior:

MethodReturnsWhen the path is missing (404)`read($path, $version = null)``array|null``null` (also `null` if a 200 response carries no `data.data`)`write($path, array $data)``void`— (creates the path)`list($path)``array` (keys directly under the path)`[]``delete($path)``void`no-op`destroy($path)``void`no-op`delete()` soft-deletes the latest version — KV2 keeps it recoverable via Vault's undelete endpoint until it's destroyed. `destroy()` permanently removes **all**versions and metadata and cannot be undone. Any other failure throws (see [Errors](#errors)).

### Authentication

[](#authentication)

`authenticate()` runs automatically on the first call and the token is cached for the process lifetime. With AppRole the client re-authenticates once on a `401/403`and retries the failed request, so an expired token is transparently refreshed. The `token` auth method uses a pre-issued token and cannot self-refresh.

### Errors

[](#errors)

Every failure surfaces as a single exception type, `Smithingdev\Vault\Exceptions\VaultException`(a `RuntimeException`), so you only have to catch one thing:

```
use Smithingdev\Vault\Exceptions\VaultException;

try {
    $secrets = Vault::read('myapp/database');
} catch (VaultException $e) {
    // log, alert, fall back, ...
}
```

A `VaultException` is thrown when:

- Vault returns a failing HTTP status other than the not-found cases handled above (read/list/delete/destroy treat 404 as "missing", not an error).
- A transport-level problem occurs (timeout, DNS, connection refused) — these are wrapped so you never have to catch Guzzle's `ConnectionException` separately.
- Authentication is misconfigured (no token for `token` auth, missing AppRole credentials) or login fails.
- `namespace_mode` is set to anything other than `header` or `path`.

Exception messages are intentionally generic (e.g. `Vault read 'foo' failed (HTTP 500).`). The raw Vault response body is **not** included in the message — it is sent to the configured log channel instead (see [Logging](#logging)), so secrets and internal details don't leak into stack traces or error reporting.

### Multiple namespaces

[](#multiple-namespaces)

To promote secrets between environments with a single login, derive a clone bound to a sibling namespace. The clone shares the already-acquired token, so no second login happens.

In **header mode**, swap the Enterprise namespace with `withNamespace()`:

```
$prod = app(VaultService::class)->withNamespace('acme/prod');
$prod->write('myapp/database', $secrets);
```

In **path mode**, swap the full KV namespace path with `withKvNamespace()`:

```
$prod = app(VaultService::class)->withKvNamespace('acme/prod/kv-secrets');
$prod->write('myapp/database', $secrets);
```

Each setter has a matching getter for reading what the instance is currently bound to — useful for deriving a sibling target from the current one:

```
Vault::namespace();    // current Enterprise namespace (header mode), e.g. "acme/dev"
Vault::kvNamespace();  // current KV namespace path (path mode), e.g. "acme/dev/kv-secrets"
```

What this package is not
------------------------

[](#what-this-package-is-not)

It deliberately ships only auth + KV2 CRUD. Application-specific concepts belong in your app, not here.

Testing
-------

[](#testing)

```
composer install
./vendor/bin/pest
```

###  Health Score

38

—

LowBetter than 83% of packages

Maintenance90

Actively maintained with recent releases

Popularity13

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity36

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Unknown

Total

1

Last Release

47d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a2e1b494339d1174075516870c7f6f8ddb4aa78e364e5f709fbf4b6c962ac7e0?d=identicon)[smithingdev](/maintainers/smithingdev)

---

Top Contributors

[![smithingdev](https://avatars.githubusercontent.com/u/257023556?v=4)](https://github.com/smithingdev "smithingdev (5 commits)")

---

Tags

laravelvaultsecretshashicorpapprolekv2

###  Code Quality

TestsPest

Static AnalysisPHPStan

### Embed Badge

![Health badge](/badges/smithingdev-laravel-vault/health.svg)

```
[![Health](https://phpackages.com/badges/smithingdev-laravel-vault/health.svg)](https://phpackages.com/packages/smithingdev-laravel-vault)
```

###  Alternatives

[psalm/plugin-laravel

Psalm plugin for Laravel

3355.4M352](/packages/psalm-plugin-laravel)[laravel/mcp

Rapidly build MCP servers for your Laravel applications.

78727.1M205](/packages/laravel-mcp)[api-platform/laravel

API Platform support for Laravel

58174.6k18](/packages/api-platform-laravel)[forjedio/inertia-table

Backend-driven dynamic tables for Laravel + Inertia.js

272.0k](/packages/forjedio-inertia-table)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
