How Logto Handles Secrets Management: A Deep Dive into the Secret Vault Architecture

Logto implements a two-layer encryption system using a runtime Key-Encryption-Key (KEK) and per-secret Data-Encryption-Keys (DEK) with AES-256-GCM, storing encrypted payloads in a PostgreSQL-backed Secret Vault with automatic relational cleanup.

Logto is an open-source identity management platform that securely stores sensitive credentials like OAuth tokens and SSO configurations. The platform's Secret Vault architecture ensures that all sensitive data is encrypted at rest using industry-standard algorithms while maintaining strict relational integrity to prevent orphaned data.

Architectural Overview of the Secret Vault

The Secret Vault follows an "encrypt-at-rest, decrypt-on-demand" model designed to protect credentials that must persist beyond a single user session. This includes access tokens, refresh tokens, and enterprise SSO configurations.

The Two-Layer Encryption Model (KEK + DEK)

Logto employs a hierarchical key structure to balance security with operational flexibility:

  • Key-Encryption-Key (KEK) – A 256-bit master key supplied at runtime via the SECRET_VAULT_KEK environment variable. This key is accessed through EnvSet.values.secretVaultKek and remains in memory only during application execution.
  • Data-Encryption-Key (DEK) – A unique 256-bit key generated for every secret stored in the vault. The DEK encrypts the actual payload, while the KEK encrypts the DEK itself.

This separation ensures that compromising a single secret's DEK does not expose other secrets, and the KEK can be rotated without re-encrypting all stored data immediately.

Encryption Algorithm and Implementation

All cryptographic operations use AES-256-GCM (Galois/Counter Mode) with authenticated encryption. The implementation in [packages/core/src/utils/secret-encryption.ts](https://github.com/logto-io/logto/blob/master/packages/core/src/utils/secret-encryption.ts) provides four core functions:

  • encryptSecret(plaintext: string) – Generates a DEK, encrypts the plaintext, then encrypts the DEK with the KEK. Returns a payload containing the initialization vector (iv), authentication tag (authTag), ciphertext, and encrypted DEK.
  • decryptSecret(encryptedPayload) – Decrypts the DEK using the KEK, then decrypts the ciphertext using the recovered DEK.
  • encryptTokens(tokenSet) – A wrapper that serializes a token set object to JSON before encryption.
  • decryptTokens(encryptedPayload) – Decrypts and parses the JSON back into a typed TokenSet object.

If the SECRET_VAULT_KEK environment variable is missing, both encryptSecret and decryptSecret throw explicit errors to prevent accidental plaintext storage or decryption failures.

Database Schema and Storage

Encrypted secrets persist in PostgreSQL with strict type safety and relational constraints.

The Secrets Table Structure

The secrets table stores the encrypted payload alongside metadata. The TypeScript definitions in [packages/schemas/src/types/secrets.ts](https://github.com/logto-io/logto/blob/master/packages/schemas/src/types/secrets.ts) enforce the schema structure:

  • id – UUID primary key
  • tenantId – Multi-tenancy isolation
  • encryptedPayload – Contains iv, authTag, ciphertext, and encryptedDek as a JSON object
  • createdAt / updatedAt – Audit timestamps

The actual database definition in packages/schemas/src/db-entries/secret.js maps these fields to PostgreSQL columns with appropriate constraints.

Relational Cleanup with Foreign Key Triggers

To prevent orphaned secrets when connectors or identities are deleted, Logto maintains two junction tables created by the migration [1.30.0-1751255436-split-secret-connector-relations-table.ts](https://github.com/logto-io/logto/blob/master/packages/schemas/alterations/1.30.0-1751255436-split-secret-connector-relations-table.ts):

  • secret_social_connector_relations – Links secrets to social connectors and user-social identities
  • secret_enterprise_sso_connector_relations – Links secrets to enterprise SSO configurations

These tables include database triggers that automatically delete the associated secret record when the parent connector or identity is removed, ensuring referential integrity without application-level cleanup logic.

Core Implementation Files

Encryption Utilities

The [packages/core/src/utils/secret-encryption.ts](https://github.com/logto-io/logto/blob/master/packages/core/src/utils/secret-encryption.ts) file contains the cryptographic engine. When encryptTokens is called, it:

  1. Serializes the token set to JSON
  2. Generates a random 256-bit DEK using crypto.randomBytes(32)
  3. Encrypts the JSON with AES-256-GCM, producing an IV, auth tag, and ciphertext
  4. Encrypts the DEK with the KEK using the same algorithm
  5. Returns a structured object ready for database insertion

Environment Configuration

The KEK is loaded from the environment in [packages/shared/src/node/env/GlobalValues.ts](https://github.com/logto-io/logto/blob/master/packages/shared/src/node/env/GlobalValues.ts). The EnvSet class validates that SECRET_VAULT_KEK is present at startup, throwing a fatal error if the variable is undefined or empty.

Database Access Layer

The SecretQueries class in packages/core/src/queries/secret.ts provides type-safe database operations:

  • insert(secret) – Persists a new encrypted secret with tenant isolation
  • findById(id) – Retrieves a secret by UUID, respecting Row-Level Security (RLS) policies
  • delete(id) – Removes a secret and cascades through relation tables

Practical Usage in Connectors

Social and enterprise connectors use the Secret Vault to store OAuth tokens securely.

Social and Enterprise Connector Integration

When a connector completes an OAuth flow, the route handler in [packages/core/src/routes/connector/index.ts](https://github.com/logto-io/logto/blob/master/packages/core/src/routes/connector/index.ts) or [packages/core/src/routes/sso-connector/index.ts](https://github.com/logto-io/logto/blob/master/packages/core/src/routes/sso-connector/index.ts) encrypts the token set before persistence.

import { encryptTokens } from '@logto/core/src/utils/secret-encryption.js';
import { SecretQueries } from '@logto/core/src/queries/secret.js';

// Encrypt a token set before database storage
const tokenSet = {
  accessToken: 'eyJhbGciOiJIUzI1NiIs...',
  refreshToken: 'dGhpcyBpcyBhIHJlZnJlc2g...',
  scope: 'openid profile email',
  expiresAt: Date.now() + 3600000,
};

const encrypted = encryptTokens(tokenSet);

await new SecretQueries().insert({
  tenantId: 'tenant_123',
  userId: 'user_456',
  secret: encrypted,
});

When the connector needs to call an external API, it retrieves and decrypts the tokens:

import { decryptTokens } from '@logto/core/src/utils/secret-encryption.js';
import { SecretQueries } from '@logto/core/src/queries/secret.js';

// Retrieve and decrypt for API calls
const stored = await new SecretQueries().findById(secretId);
const tokenSet = decryptTokens(stored.secret);

// Use tokenSet.accessToken for the Authorization header
const response = await fetch('https://api.example.com/userinfo', {
  headers: { Authorization: `Bearer ${tokenSet.accessToken}` },
});

Summary

  • Two-layer encryption – Logto uses a runtime KEK to encrypt per-secret DEKs, ensuring isolation between credentials.
  • AES-256-GCM – All encryption uses authenticated encryption with 256-bit keys and random initialization vectors.
  • Automatic cleanup – Database triggers in relation tables prevent orphaned secrets when connectors are deleted.
  • Environment enforcement – The application refuses to start without SECRET_VAULT_KEK, preventing accidental plaintext storage.
  • Type-safe queries – The SecretQueries class encapsulates all database operations with tenant isolation.

Frequently Asked Questions

What happens if SECRET_VAULT_KEK is missing?

Logto validates the SECRET_VAULT_KEK environment variable during startup in [GlobalValues.ts](https://github.com/logto-io/logto/blob/master/packages/shared/src/node/env/GlobalValues.ts). If the variable is undefined or empty, the application throws a fatal error and exits. This prevents the system from running in an insecure state where secrets might be stored unencrypted or decryption might fail silently.

How does Logto prevent orphaned secrets?

The platform uses foreign key relations and database triggers defined in the split-secret-connector-relations-table migration. When a social connector, enterprise SSO connector, or user identity is deleted, the associated trigger automatically removes the corresponding entry from the junction table and deletes the secret from the vault, maintaining referential integrity without requiring application-level cleanup code.

What encryption algorithm does Logto use for secrets?

Logto uses AES-256-GCM (Advanced Encryption Standard with Galois/Counter Mode) for all secret operations. This provides authenticated encryption, meaning the decryption process verifies data integrity using the authentication tag before returning plaintext, preventing tampering attacks.

Can I rotate the Key-Encryption-Key without data loss?

Yes, but it requires a re-encryption process. Since each secret stores its DEK encrypted by the KEK, you can decrypt all secrets using the old KEK, then re-encrypt the DEKs with a new KEK. The per-secret DEKs remain unchanged during this process, so the actual credential data stays intact. Logto does not currently provide an automated rotation utility, so this must be implemented as a custom migration script using the decryptSecret and encryptSecret functions from [secret-encryption.ts](https://github.com/logto-io/logto/blob/master/packages/core/src/utils/secret-encryption.ts).

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →