Banata

Enterprise

Vault & Encryption

Application-level encryption for sensitive data with key management and field-level encryption support.

The Vault gives you an application-level encryption layer for sensitive data — PII, tokens, certificates, and secrets. Instead of relying solely on database-level encryption, the Vault encrypts values before they reach storage. Even with direct database access, your secret data stays unreadable without the correct key.


How It Works

The Vault uses AES-256-GCM (Galois/Counter Mode) authenticated encryption. Here's what happens under the hood:

typescript
1. An encryption key is derived from BETTER_AUTH_SECRET using HKDF
2. A unique initialization vector (IV) is generated per secret
3. The plaintext is encrypted with AES-256-GCM, producing ciphertext + auth tag
4. The encrypted value, IV, and metadata are stored in the vaultSecret table
5. On decryption, the IV and derived key reconstruct the plaintext
6. The GCM auth tag guarantees integritytampered ciphertext is rejected

What Gets Encrypted

The Vault automatically encrypts sensitive data across Banata Auth's internal subsystems:

DataSubsystemWhy
SAML IdP certificatesSSOX.509 certificates are long-lived credentials
OIDC client secretsSSOOAuth client secrets for OIDC connections
SCIM bearer tokensDirectory SyncTokens used for SCIM provisioning API calls
Webhook signing secretsWebhooksHMAC secrets used to sign outbound webhook payloads
Email provider API keysEmailCredentials for SendGrid, Resend, Postmark, etc.
Custom secretsYour applicationAny sensitive data you encrypt via the SDK

Configuration

The Vault is enabled by default when the BETTER_AUTH_SECRET environment variable is set in your deployment. No additional configuration is required — the encryption key is derived directly from this secret.

Critical: BETTER_AUTH_SECRET is the root of all Vault encryption. If you lose or change this secret without rotating Vault keys first, all encrypted data becomes permanently unrecoverable.

Requirements

  • BETTER_AUTH_SECRET must be at least 32 characters
  • Use a cryptographically random value (e.g., openssl rand -base64 48)
  • Store it in your deployment's environment variables, not in source control

SDK Usage

Encrypting Data

Store a secret in the Vault:

typescript
import { BanataAuth } from "@banata-auth/sdk";
 
const banata = new BanataAuth({
  apiKey: "sk_live_...",
  baseUrl: "https://auth.banata.dev",
});
 
// Encrypt and store a secret
const { id } = await banata.vault.encrypt({
  name: "stripe-api-key",
  data: "sk_live_abc123...",
  // Optional: bind to a context for key separation
  context: "payment-processing",
  // Optional: scope to an organization
  organizationId: "org_01HXYZ...",
  // Optional: attach metadata
  metadata: { provider: "stripe", environment: "production" },
});
 
console.log(id); // "vsec_01HXYZ..."

The context parameter provides cryptographic context binding — secrets encrypted with one context cannot be decrypted with a different context, even though the same master key is used. Use this to isolate secrets by purpose.

Decrypting Data

Retrieve and decrypt a stored secret:

typescript
const { data } = await banata.vault.decrypt({
  secretId: "vsec_01HXYZ...",
  // Must match the context used during encryption
  context: "payment-processing",
});
 
console.log(data); // "sk_live_abc123..."

Listing Secrets

List Vault secrets (metadata only — encrypted values are never returned in list responses):

typescript
const result = await banata.vault.list({
  organizationId: "org_01HXYZ...", // Optional: filter by org
  limit: 25,
});
 
for (const secret of result.data) {
  console.log(secret.id);             // "vsec_01HXYZ..."
  console.log(secret.name);           // "stripe-api-key"
  console.log(secret.context);        // "payment-processing"
  console.log(secret.organizationId); // "org_01HXYZ..."
  console.log(secret.metadata);       // { provider: "stripe", ... }
  console.log(secret.createdAt);      // Date
}

Deleting a Secret

Permanently remove a secret from the Vault:

typescript
await banata.vault.delete({
  secretId: "vsec_01HXYZ...",
});

This is irreversible. The encrypted data is permanently deleted.

Key Rotation

Rotate the Vault encryption key. This re-encrypts all existing secrets with a new key derived from the current BETTER_AUTH_SECRET:

typescript
const result = await banata.vault.rotateKey();
console.log(result.status); // "completed"

Key rotation increments the version on all re-encrypted secrets. You should rotate keys periodically (e.g., every 90 days) or immediately if a key compromise is suspected.


Envelope Encryption

The Vault uses envelope encryption to balance security and performance:

typescript
                    BETTER_AUTH_SECRET
                           |
                     HKDF derivation
                           |
                    Data Encryption Key (DEK)
                           |
                ┌──────────┴──────────┐
                |                     |
          AES-256-GCM           AES-256-GCM
          encrypt(secret1)      encrypt(secret2)
                |                     |
          encryptedValue +      encryptedValue +
          IV stored in DB       IV stored in DB
  • HKDF (HMAC-based Key Derivation Function) derives a unique DEK from the master secret and optional context
  • AES-256-GCM provides authenticated encryption (confidentiality + integrity)
  • Each secret gets a unique IV (initialization vector), ensuring identical plaintexts produce different ciphertexts
  • The GCM authentication tag detects any tampering with the ciphertext

Security Best Practices

  1. Protect BETTER_AUTH_SECRET — This is the root of all Vault encryption. Use a secrets manager (AWS Secrets Manager, Vercel env vars, etc.) and never commit it to source control.
  2. Use context strings — Bind secrets to their intended purpose. A secret encrypted with context: "email" cannot be decrypted with context: "payments".
  3. Rotate keys regularly — Call vault.rotateKey() periodically. This re-encrypts all secrets without downtime.
  4. Scope to organizations — In multi-tenant applications, use organizationId to ensure secrets are isolated per tenant.
  5. Audit access — Vault operations generate audit events. Monitor vault.encrypt, vault.decrypt, and vault.rotate_key events for anomalous access patterns.

Audit Events

EventWhen
vault.encryptedA new secret is stored in the Vault
vault.decryptedA secret is decrypted and read
vault.deletedA secret is permanently removed
vault.key_rotatedThe Vault encryption key was rotated

Troubleshooting

"Decryption failed"

  1. Verify the context parameter matches the value used during encryption.
  2. Ensure BETTER_AUTH_SECRET has not been changed since the secret was encrypted.
  3. Check that the secret has not been corrupted — GCM will reject tampered data.

"Vault secret not found"

  1. Confirm the secretId is correct (it should start with vsec_).
  2. The secret may have been deleted.
  3. Check organization scoping — secrets scoped to one org are not visible to another.

"BETTER_AUTH_SECRET is required"

Set the BETTER_AUTH_SECRET environment variable in your deployment. It must be at least 32 characters.


Next Steps

  • SDK Reference — Complete API reference for all Vault methods
  • Audit Logs — Monitor Vault access events
  • Environment Variables — Configure BETTER_AUTH_SECRET and other secrets
  • SSO & SAML — See how the Vault protects SSO credentials automatically
  • Webhooks — Learn how webhook signing secrets are secured