Securely Managing Application Secrets in Logto: The Complete Guide to KEK and DEK Encryption
Logto protects OAuth tokens and sensitive data using a hierarchical encryption system where a Key Encryption Key (KEK) encrypts per-request Data Encryption Keys (DEKs), ensuring secrets remain confidential at rest.
Securely managing application secrets in Logto relies on a robust secret vault architecture implemented in the logto-io/logto repository. This system encrypts sensitive data such as OAuth token sets before persistence, using AES-256-GCM encryption with a two-tier key hierarchy. The implementation ensures that even if database storage is compromised, secrets remain inaccessible without the environment-specific KEK.
Understanding Logto's Secret Vault Architecture
Logto's encryption model follows the envelope encryption pattern, separating the duties of key management from data encryption. This architecture is defined in packages/core/src/utils/secret-encryption.ts and relies on environment configuration managed through packages/shared/src/node/env/GlobalValues.ts.
The Key Encryption Key (KEK)
The Key Encryption Key (KEK) serves as the root of trust for the entire secret vault. Logto reads this from the SECRET_VAULT_KEK environment variable, exposed via EnvSet.values.secretVaultKek in GlobalValues.ts (line 268). This 256-bit key must be generated once during deployment and remains constant for the application lifecycle.
The Data Encryption Key (DEK) per Request
For every encryption operation, Logto generates a unique Data Encryption Key (DEK) using crypto.randomBytes(32). This creates a fresh 256-bit key for AES-256-GCM encryption. The DEK encrypts the actual secret payload, while the KEK encrypts the DEK itself. This approach ensures that even if a single DEK is compromised, other secrets remain secure, and it enables secure key rotation without re-encrypting all data.
How Encryption Works in Logto's Source Code
The encryption workflow in packages/core/src/utils/secret-encryption.ts follows five distinct steps:
- Fetch the KEK – The system retrieves
EnvSet.values.secretVaultKekfrom the process environment. - Generate a DEK – A new 256-bit key is created via
crypto.randomBytes(32). - Encrypt the secret – The plaintext (e.g., a JSON-encoded token set) is encrypted with the DEK, producing an initialization vector (IV), ciphertext, and authentication tag.
- Encrypt the DEK – The DEK itself is encrypted using the KEK, forming an
encryptedDekstructure containing IV, encrypted key material, and auth tag. - Return an EncryptedSecret – The final object contains
{ iv, authTag, ciphertext, encryptedDek }.
Decryption reverses this process: the KEK decrypts the DEK, then the DEK decrypts the secret payload. The serializeEncryptedSecret and deserializeEncryptedSecret functions handle conversion to and from base64 strings for database storage.
Configuration and Environment Setup
Before utilizing the secret vault, you must configure the KEK environment variable. Logto validates this configuration at runtime before enabling secret-dependent features.
Generate a secure 256-bit key and encode it as base64:
node -e "console.log(require('crypto').randomBytes(32).toString('base64'))"
Set the output as the SECRET_VAULT_KEK environment variable in your deployment configuration (Docker Compose, .env file, or cloud provider secret manager):
SECRET_VAULT_KEK=<base64-32-byte-key>
The EnvSet loader in packages/core/src/env-set/index.ts makes these values globally available across modules, ensuring consistent access to secretVaultKek throughout the application.
Practical Implementation Examples
Encrypting OAuth Token Sets
When storing tokens after a successful OAuth flow, use the encryptTokens function from packages/core/src/utils/secret-encryption.ts:
import { encryptTokens, serializeEncryptedSecret } from '#src/utils/secret-encryption.js';
const tokenSet = {
access_token: 'abc123',
refresh_token: 'def456',
// Additional fields like id_token, scope, etc.
};
const encrypted = encryptTokens(tokenSet);
const storedBase64 = serializeEncryptedSecret(encrypted);
// Store `storedBase64` in your database
Decrypting Stored Secrets
Retrieve and decrypt secrets using the corresponding deserialization and decryption functions:
import { deserializeEncryptedSecret, decryptTokens } from '#src/utils/secret-encryption.js';
// Assume `storedBase64` was read from the database
const encrypted = deserializeEncryptedSecret(storedBase64);
const decryptedTokenSet = decryptTokens(encrypted);
// `decryptedTokenSet` matches the original `tokenSet` structure
Using the Helper with Metadata
For convenience, Logto provides encryptAndSerializeTokenResponse which handles both encryption and metadata extraction:
import { encryptAndSerializeTokenResponse } from '#src/utils/secret-encryption.js';
import type { TokenResponse } from '@logto/connector-kit';
const tokenResponse: TokenResponse = {
access_token: 'xyz',
refresh_token: 'refresh-token-value',
scope: 'read write'
};
const encryptedTokenSet = encryptAndSerializeTokenResponse(tokenResponse);
// Store `encryptedTokenSet.encryptedTokenSetBase64` in the database
// Use `encryptedTokenSet.metadata` for quick lookup without decryption
Security Guards and Validation
Logto enforces KEK presence before allowing secret storage operations. In packages/core/src/routes/sso-connector/index.ts (line 31), the SSO connector routes check for secretVaultKek before enabling token storage functionality. This guard ensures that secret-related features are disabled if the vault is improperly configured, preventing accidental plaintext storage of sensitive credentials.
The encrypted payload structure ensures confidentiality at rest and portability—the base64-encoded EncryptedSecret can be migrated between environments or backed up without exposing cleartext data, provided the KEK remains secure.
Summary
- Logto implements a two-tier encryption hierarchy using a static Key Encryption Key (KEK) and per-request Data Encryption Keys (DEK).
- The
SECRET_VAULT_KEKenvironment variable must be set to a 256-bit base64-encoded key before enabling secret storage features. - Encryption logic resides in
packages/core/src/utils/secret-encryption.ts, supporting AES-256-GCM with authentication tags. - Serialization utilities convert encrypted objects to base64 strings for safe database persistence.
- Runtime guards in
packages/core/src/routes/sso-connector/index.tsprevent secret storage when the vault is unconfigured. - Secrets are only recoverable by services possessing the correct
SECRET_VAULT_KEK, ensuring confidentiality even if database storage is compromised.
Frequently Asked Questions
What is the difference between KEK and DEK in Logto?
The Key Encryption Key (KEK) is a long-term master key stored in the SECRET_VAULT_KEK environment variable, while the Data Encryption Key (DEK) is a temporary, randomly generated key created for each encryption operation. The DEK encrypts the actual secret data, and the KEK encrypts the DEK. This separation allows for secure key rotation and limits exposure if a single encryption key is compromised.
How do I generate a secure SECRET_VAULT_KEK?
Generate a cryptographically secure 256-bit key using Node.js crypto and encode it as base64: node -e "console.log(require('crypto').randomBytes(32).toString('base64'))". Store this value in your environment configuration as SECRET_VAULT_KEK. This key must be consistent across all Logto instances sharing the same database and should be backed up securely—if lost, encrypted secrets cannot be recovered.
Where does Logto store the encrypted secrets?
Logto stores the serialized encrypted secrets in the configured database (PostgreSQL by default) as base64-encoded strings. The actual encryption and decryption occur in memory within the application layer using packages/core/src/utils/secret-encryption.ts. The database only ever sees the encrypted payload containing the ciphertext, IV, auth tag, and encrypted DEK—never the plaintext or the KEK.
What happens if the SECRET_VAULT_KEK is lost?
If the SECRET_VAULT_KEK is lost or changed without migrating existing data, all encrypted secrets become permanently inaccessible. The KEK is required to decrypt the DEK, which in turn decrypts the secret payload. There is no recovery mechanism or backdoor—this design ensures security through true cryptographic separation. Always maintain secure backups of your KEK in a hardware security module (HSM) or enterprise secret manager.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →