Skip to content

Encryption

Every secret stored in Authpipe is encrypted with AES-256-GCM before being written to the database. This includes:

DataEncrypted?
OAuth client_secretYes
Provider signing_keyYes
Connection access_tokenYes
Connection refresh_tokenYes
Installation access_tokenYes
Installation refresh_tokenYes
Webhook signing secrets (secret_1, secret_2)Yes
Stored API keys and webhook secrets (via storeCredential)Yes

Non-secret metadata is stored in plaintext for queryability:

  • client_id — needed for OAuth URL generation
  • tenant_id, user_id — needed for lookups
  • external_user_id — provider’s user identifier
  • scopes — granted OAuth scopes
  • status — connection/installation status
  • Timestamps (created_at, token_expires_at, etc.)

Each workspace has its own encryption_key_id. This means:

  • Credentials in workspace A cannot be decrypted with workspace B’s key
  • A compromised key only affects one workspace
  • Key rotation can be done per-workspace without affecting others
  1. Write path: When a secret is stored (OAuth token exchange, storeCredential, etc.), the plaintext value is encrypted with the workspace’s AES-256-GCM key and stored as bytea in PostgreSQL.
  2. Read path: When getCredential is called, the encrypted bytes are decrypted at the point of use and returned in the response. The plaintext is never written to disk or logs.

Encrypted fields are marked json:"-" on domain structs, preventing accidental serialization. The fields AccessTokenEncrypted, RefreshTokenEncrypted, ClientSecretEncrypted, and SigningKeyEncrypted are never included in API responses.

The decrypted access_token value is only returned through the getCredential endpoint, which is the single authorized exit point for secrets.

Encryption keys are managed outside the database. The encryption_key_id on the workspace references an external key store. Authpipe never stores raw encryption keys in the same database as the encrypted data.