# How TeslaMate Handles API Token Encryption: A Complete Technical Guide

> Learn how TeslaMate encrypts API tokens at rest using AES-GCM and Cloak for secure PostgreSQL storage. Discover transparent decryption with Ecto.

- Repository: [TeslaMate/teslamate](https://github.com/teslamate-org/teslamate)
- Tags: deep-dive
- Published: 2026-06-18

---

**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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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:

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

```

### Manually Storing Token Pairs

Save tokens programmatically through the Auth context:

```elixir

# 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:

```elixir
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:

```elixir
:ok = TeslaMate.Auth.delete_tokens()

```

## Summary

- **AES-GCM Encryption**: TeslaMate uses Cloak Vault with AES-GCM encryption, configured in [`lib/teslamate/vault.ex`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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.