← Back to Blog
|9 min read|Vipula Saman Anandapiya

OpenBao with Laravel: Installation, Usage, and Cross-System Authentication

OpenBao with Laravel: Installation, Usage, and Cross-System Authentication

OpenBao with Laravel: What It Is, How It Works, and How to Use It

OpenBao is a modern secrets management and identity-based access system (a community-driven fork in the HashiCorp Vault ecosystem). It helps you store and control access to secrets such as API keys, database passwords, certificates, and encryption keys—without hardcoding them into your application or shipping them in environment files.

What is OpenBao?

OpenBao is a secrets management server that provides:

  • Secure secret storage (encrypted at rest, access controlled)
  • Dynamic secrets (e.g., short-lived DB credentials)
  • Authentication methods (AppRole, JWT/OIDC, Kubernetes, etc.)
  • Authorization via policies (fine-grained ACL rules)
  • Auditing (who accessed what, when)

In practical terms, OpenBao becomes the central authority for secrets and service identity in your infrastructure.

How OpenBao Works (High-Level)

  1. Start and initialize the server (it generates master keys).
  2. Unseal the server using unseal keys (or auto-unseal via a KMS in production).
  3. Enable auth methods (e.g., AppRole for services, or JWT/OIDC for users).
  4. Write policies defining what a token can read/write.
  5. Applications authenticate to get a short-lived token.
  6. Applications use the token to read secrets or perform operations.

OpenBao tokens are the core mechanism: once a client authenticates, it receives a token bound to policies and TTL (time-to-live).

Installation (Local / Dev) with Docker

This section sets up OpenBao quickly for local development. For production, you should use TLS, persistent storage, HA, and auto-unseal.

1) Create a Docker Compose file

mkdir openbao-lab && cd openbao-lab
cat > docker-compose.yml <<'YML'
services:
  openbao:
    image: openbao/openbao:latest
    container_name: openbao
    ports:
      - "8200:8200"
    environment:
      BAO_ADDR: "http://0.0.0.0:8200"
      BAO_API_ADDR: "http://127.0.0.1:8200"
    cap_add:
      - IPC_LOCK
    command: server -dev -dev-root-token-id="root"
YML

docker compose up -d

Notes:

  • server -dev is development mode: in-memory storage, auto-unsealed, not for production.
  • We set a known dev root token: root.

2) Install the CLI (optional but recommended)

OpenBao provides a CLI similar to Vault. Depending on your OS, install from the official OpenBao releases. If you can’t install it, you can still use the HTTP API directly (we’ll do that from Laravel).

3) Export environment variables

export BAO_ADDR="http://127.0.0.1:8200"
export BAO_TOKEN="root"

4) Verify server is reachable

curl -s $BAO_ADDR/v1/sys/health | jq

Configure OpenBao: KV Secrets Engine + Policy + AppRole

We’ll enable a KV (Key/Value) secrets engine and set up an AppRole for Laravel services. AppRole is a common pattern for machine-to-machine authentication.

1) Enable KV v2 at path secret/

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"type":"kv","options":{"version":"2"}}' \
  http://127.0.0.1:8200/v1/sys/mounts/secret | jq

2) Write a sample secret

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"data":{"DB_PASSWORD":"super-secret","API_KEY":"abc-123"}}' \
  http://127.0.0.1:8200/v1/secret/data/laravel/app | jq

3) Create a policy for Laravel to read only what it needs

Policy example: allow read of secret/data/laravel/app only.

cat > laravel-read.hcl <<'HCL'
path "secret/data/laravel/app" {
  capabilities = ["read"]
}
HCL

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"policy":"'"$(sed ':a;N;$!ba;s/\n/\\n/g' laravel-read.hcl)'""}' \
  http://127.0.0.1:8200/v1/sys/policies/acl/laravel-read | jq

4) Enable AppRole auth method

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"type":"approle"}' \
  http://127.0.0.1:8200/v1/sys/auth/approle | jq

5) Create an AppRole for a Laravel service

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{
    "token_policies":["laravel-read"],
    "token_ttl":"15m",
    "token_max_ttl":"60m"
  }' \
  http://127.0.0.1:8200/v1/auth/approle/role/laravel-service | jq

6) Fetch Role ID and generate a Secret ID

# Role ID
curl -s \
  -H "X-Vault-Token: root" \
  http://127.0.0.1:8200/v1/auth/approle/role/laravel-service/role-id | jq

# Secret ID
curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  http://127.0.0.1:8200/v1/auth/approle/role/laravel-service/secret-id | jq

Store the role_id and secret_id securely (e.g., container secrets / CI secrets). They are the credentials your Laravel app uses to log in and obtain a short-lived token.

Using OpenBao with Laravel

There are PHP clients for Vault-like APIs, but you can also use Laravel’s built-in HTTP client for a clean, dependency-light integration.

1) Add configuration to .env

OPENBAO_ADDR=http://127.0.0.1:8200
OPENBAO_APPROLE_ROLE_ID=your_role_id_here
OPENBAO_APPROLE_SECRET_ID=your_secret_id_here

2) Create config/openbao.php

<?php

return [
    'addr' => env('OPENBAO_ADDR', 'http://127.0.0.1:8200'),
    'approle' => [
        'role_id' => env('OPENBAO_APPROLE_ROLE_ID'),
        'secret_id' => env('OPENBAO_APPROLE_SECRET_ID'),
    ],
];

3) Implement an OpenBao service class

Create app/Services/OpenBaoClient.php:

<?php

namespace App\Services;

use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\Http;

class OpenBaoClient
{
    public function __construct(
        private readonly string $addr,
        private readonly string $roleId,
        private readonly string $secretId,
    ) {}

    public static function make(): self
    {
        return new self(
            config('openbao.addr'),
            config('openbao.approle.role_id'),
            config('openbao.approle.secret_id'),
        );
    }

    /**
     * Login using AppRole and return a client token.
     * Cache it until near expiry.
     */
    public function token(): string
    {
        return Cache::remember('openbao_token', now()->addMinutes(14), function () {
            $resp = Http::timeout(5)
                ->post($this->addr.'/v1/auth/approle/login', [
                    'role_id' => $this->roleId,
                    'secret_id' => $this->secretId,
                ]);

            $resp->throw();

            return $resp->json('auth.client_token');
        });
    }

    /**
     * Read a KV v2 secret.
     * Example path: secret/data/laravel/app
     */
    public function read(string $path): array
    {
        $token = $this->token();

        $resp = Http::timeout(5)
            ->withHeaders(['X-Vault-Token' => $token])
            ->get($this->addr.'/v1/'.$path);

        $resp->throw();

        // KV v2: data.data contains actual key-values
        return $resp->json('data.data') ?? [];
    }
}

4) Use it in Laravel code

use App\Services\OpenBaoClient;

Route::get('/secrets', function () {
    $bao = OpenBaoClient::make();
    $secrets = $bao->read('secret/data/laravel/app');

    return response()->json([
        'db_password' => $secrets['DB_PASSWORD'] ?? null,
        'api_key' => $secrets['API_KEY'] ?? null,
    ]);
});

Best practice: don’t return secrets in HTTP responses in real systems. Use them internally (e.g., configure DB connections, sign requests, etc.).

Sample Scenario: Cross-System Authentication via OpenBao

Let’s connect two systems through OpenBao:

  • System A: Laravel app that authenticates to OpenBao and generates a short-lived token intended for System B’s use.
  • System B: Another service that receives that token, verifies it against OpenBao, and uses it to read only the allowed secrets.

Important security note: In many architectures, you do not pass OpenBao tokens between systems. A more robust pattern is: both systems authenticate independently (AppRole/JWT/K8s) and OpenBao policies enforce access. However, for your requested scenario, we’ll implement “token minting” on System A and “token verification + usage” on System B.

Step 1: Create a policy for System B token

This token should only be allowed to read a specific secret path (or a narrow set of paths).

cat > system-b-read.hcl <<'HCL'
path "secret/data/shared/integration" {
  capabilities = ["read"]
}
HCL

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"policy":"'"$(sed ':a;N;$!ba;s/\n/\\n/g' system-b-read.hcl)'""}' \
  http://127.0.0.1:8200/v1/sys/policies/acl/system-b-read | jq

Step 2: Write the shared secret that System B can read

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"data":{"INTEGRATION_SECRET":"shared-value-xyz"}}' \
  http://127.0.0.1:8200/v1/secret/data/shared/integration | jq

Step 3: Allow System A to create child tokens (token minting)

System A needs permission to create tokens with the system-b-read policy. We’ll create a policy for System A that allows token creation.

cat > system-a-mint.hcl <<'HCL'
path "auth/token/create" {
  capabilities = ["create", "update"]
}
HCL

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{"policy":"'"$(sed ':a;N;$!ba;s/\n/\\n/g' system-a-mint.hcl)'""}' \
  http://127.0.0.1:8200/v1/sys/policies/acl/system-a-mint | jq

Now attach system-a-mint to System A’s AppRole in addition to its read policy.

curl -s \
  -H "X-Vault-Token: root" \
  -X POST \
  -d '{
    "token_policies":["laravel-read","system-a-mint"],
    "token_ttl":"15m",
    "token_max_ttl":"60m"
  }' \
  http://127.0.0.1:8200/v1/auth/approle/role/laravel-service | jq

Step 4 (System A): Laravel endpoint that mints a System B token

System A logs into OpenBao via AppRole, then requests a new token with the system-b-read policy and a short TTL.

<?php

use App\Services\OpenBaoClient;
use Illuminate\Support\Facades\Http;

Route::post('/mint-token-for-b', function () {
    $bao = OpenBaoClient::make();
    $systemAToken = $bao->token();

    $resp = Http::timeout(5)
        ->withHeaders(['X-Vault-Token' => $systemAToken])
        ->post(config('openbao.addr').'/v1/auth/token/create', [
            'policies' => ['system-b-read'],
            'ttl' => '5m',
            'renewable' => false,
            // Optional: add metadata to help auditing
            'meta' => [
                'issued_by' => 'system-a',
                'intended_for' => 'system-b',
            ],
        ]);

    $resp->throw();

    return response()->json([
        'token' => $resp->json('auth.client_token'),
        'ttl' => $resp->json('auth.lease_duration'),
    ]);
});

How System A shares the token with System B: Typically via a secure server-to-server call over TLS, possibly with request signing (mTLS or HMAC) and strict logging. Treat the token like a password.

Step 5 (System B): Verify token and read the shared secret

System B receives the token and performs a lookup-self call to validate it, then uses it to read the allowed secret.

<?php

use Illuminate\Support\Facades\Http;

Route::post('/use-openbao-token', function () {
    $tokenFromA = request('token');
    abort_unless($tokenFromA, 400, 'token is required');

    $addr = env('OPENBAO_ADDR', 'http://127.0.0.1:8200');

    // 1) Validate token (cross-authenticate)
    $lookup = Http::timeout(5)
        ->withHeaders(['X-Vault-Token' => $tokenFromA])
        ->get($addr.'/v1/auth/token/lookup-self');

    $lookup->throw();

    $policies = $lookup->json('data.policies') ?? [];
    abort_unless(in_array('system-b-read', $policies, true), 403, 'token not authorized');

    // 2) Use token to read what it is allowed to read
    $secret = Http::timeout(5)
        ->withHeaders(['X-Vault-Token' => $tokenFromA])
        ->get($addr.'/v1/secret/data/shared/integration');

    $secret->throw();

    return response()->json([
        'token_valid' => true,
        'integration_secret' => $secret->json('data.data.INTEGRATION_SECRET'),
    ]);
});

End-to-end flow recap

  1. System A authenticates to OpenBao using AppRole and receives a short-lived token.
  2. System A mints a second short-lived token scoped to system-b-read.
  3. System A sends that token to System B over a secure channel.
  4. System B verifies the token with lookup-self and ensures it has the expected policy.
  5. System B uses the token to read secret/data/shared/integration.

Production Hardening Checklist (Quick)

  • Enable TLS on OpenBao and use HTTPS only.
  • Use persistent storage (Raft, Consul, etc.), not dev mode.
  • Auto-unseal with a cloud KMS/HSM if possible.
  • Short TTL tokens, minimal policies (least privilege).
  • Avoid passing tokens between systems unless you have a strong reason; prefer each system authenticating directly.
  • Audit logs enabled and monitored.
  • Rotate secrets and AppRole Secret IDs regularly.

Conclusion

OpenBao gives Laravel applications a secure, auditable way to fetch secrets on demand and to establish service identity via policies and short-lived tokens. With AppRole, you can authenticate non-interactive services cleanly, and with token minting you can implement controlled cross-system access—while keeping permissions tightly scoped.