How TeslaMate Handles API Token Encryption: A Complete Technical Guide

TeslaMate encrypts Tesla API tokens at rest using AES-GCM via the Cloak library, transparently decrypting them through Ecto field encryption whenever the application reads from the PostgreSQL database.

TeslaMate, the open-source Tesla data logger, implements robust API token encryption to protect sensitive credentials at rest in its PostgreSQL database. According to the teslamate-org/teslamate source code, the application uses a multi-layered architecture combining environment-managed encryption keys, Ecto schema annotations, and the Cloak cryptographic library. This ensures that Tesla API access and refresh tokens are never persisted in clear text while remaining accessible to the application runtime.

Encryption Key Management in TeslaMate.Vault

The encryption system centers on TeslaMate.Vault, defined in lib/teslamate/vault.ex, which configures the Cloak encryption backend and manages the AES-GCM cipher keys.

Environment Variable Configuration

The Vault requires a stable encryption key provided via the ENCRYPTION_KEY environment variable. During initialization, the init/1 function (lines 48-84) retrieves this value and hashes it using SHA-256 before storing it in the Vault's :ciphers configuration. This key derivation ensures consistent encryption across application restarts.

Runtime Key Generation Fallback

If ENCRYPTION_KEY is not set, the Vault generates a random key at runtime via get_encryption_key_from_config/0 (lines 100-106) and logs a warning. This temporary key is lost on restart, requiring users to re-authenticate with Tesla's API, which prevents permanent data loss but ensures continuous operation during initial setup.

Database Schema Encryption

Token storage encryption is implemented at the schema level in lib/teslamate/auth/tokens.ex, which defines the database structure for the tokens table.

Encrypted.Binary Field Types

The schema declares both access and refresh fields as Encrypted.Binary types (lines 11-13). This type is a thin wrapper around Cloak.Ecto.Binary, which instructs Ecto to automatically encrypt values before persisting them to PostgreSQL and decrypt them when fetching rows. The redact: true option ensures that Elixir's built-in logging mechanisms never accidentally expose raw token bytes in application logs.

Private Schema Isolation

The schema uses @schema_prefix :private to isolate token data within a dedicated PostgreSQL schema, providing an additional layer of access control beyond standard table permissions.

Token Lifecycle Management

The TeslaMate.Auth context in lib/teslamate/auth.ex provides the public API for token operations, handling the encrypted data transparently.

Storing Encrypted Tokens

The save/1 function accepts a map containing access and refresh tokens and persists them via either create_tokens/1 (for new records) or update_tokens/2 (for existing records). Because the schema fields are marked as Encrypted.Binary, Cloak automatically encrypts the plaintext values using the Vault's configured AES-GCM cipher before they reach the database.

Retrieving and Decrypting Tokens

When the application needs credentials, get_tokens/0 queries the database and returns a %TeslaMate.Auth.Tokens{} struct. Cloak decrypts the ciphertext automatically during the fetch operation, providing plaintext binaries to the calling function without requiring manual decryption code.

Validation and Error Handling

The can_decrypt_tokens?/0 helper function validates that retrieved values are valid binary strings, returning false if the encryption key is missing or mismatched. This detection mechanism allows the application to gracefully handle scenarios where the ENCRYPTION_KEY environment variable changes or is unavailable.

Runtime Integration with the Tesla API

The encryption system integrates seamlessly with TeslaMate's API client operations. When TeslaMate.Api.sign_in/2 requires credentials, it calls Auth.get_tokens/0 to obtain decrypted values. Similarly, the token refresh logic in Auth.refresh/1 operates on plaintext tokens, then persists the refreshed credentials back through Auth.save/1, which re-encrypts them before storage.

Configuration and Usage Examples

Setting the Encryption Key in Docker

Configure a persistent encryption key via the environment variable:

environment:
  ENCRYPTION_KEY: "my-super-secret-32-byte-key-generated-once"

Manually Storing Token Pairs

Save tokens programmatically through the Auth context:


# Assume you have tokens from Tesla's API

tokens = %{token: "access-xyz", refresh_token: "refresh-abc"}

# Fields are encrypted automatically before database insertion

:ok = TeslaMate.Auth.save(tokens)

Retrieving Decrypted Tokens

Access plaintext tokens for API calls:

case TeslaMate.Auth.get_tokens() do
  %TeslaMate.Auth.Tokens{access: access, refresh: refresh} ->
    # Values are decrypted binaries ready for use

    IO.puts("Access token: #{access}")

  nil ->
    IO.puts("No stored tokens - user must sign in again")
end

Deleting Stored Tokens

Remove tokens from the database during logout:

:ok = TeslaMate.Auth.delete_tokens()

Summary

  • AES-GCM Encryption: TeslaMate uses Cloak Vault with AES-GCM encryption, configured in lib/teslamate/vault.ex, to protect tokens at rest.
  • Environment-Based Keys: The system relies on the ENCRYPTION_KEY environment variable, falling back to ephemeral random keys only when necessary.
  • Transparent Field Encryption: The Encrypted.Binary type in lib/teslamate/auth/tokens.ex ensures automatic encryption/decryption through Ecto without manual cipher operations.
  • Safe Logging: The redact: true schema option prevents token exposure in application logs.
  • Validation: can_decrypt_tokens?/0 detects key mismatches before they cause runtime errors.

Frequently Asked Questions

What happens if I don't set the ENCRYPTION_KEY environment variable?

If ENCRYPTION_KEY is unset, TeslaMate.Vault generates a random key at runtime and logs a warning. While the application continues to function, this temporary key is lost on restart, rendering existing encrypted tokens unreadable and forcing users to re-authenticate with Tesla's API.

How does TeslaMate prevent API tokens from appearing in logs?

The TeslaMate.Auth.Tokens schema marks both access and refresh fields with redact: true, which instructs Elixir's inspection protocols to hide these values. Additionally, the Encrypted.Binary type ensures only ciphertext exists in database query logs and backup files.

Can I rotate the encryption key without losing my Tesla API tokens?

No, rotating the encryption key requires re-authentication. Because the AES-GCM keys are used to encrypt the tokens themselves, changing the ENCRYPTION_KEY value without decrypting and re-encrypting the existing data makes previous ciphertext unrecoverable. You must delete existing tokens via TeslaMate.Auth.delete_tokens() and complete the OAuth flow again to generate new credentials.

Where is the token encryption migration defined?

The database migration that created the encrypted tokens table is located at priv/migrations/20220123131732_encrypt_api_tokens.exs. This migration established the private.tokens table with binary columns designed to store the ciphertext produced by Cloak's AES-GCM implementation.

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 →