How TeslaMate Manages Tesla API Tokens: Encrypted Storage & Automatic Refresh

TeslaMate stores Tesla API tokens in an encrypted database table and manages their lifecycle through a GenServer that automatically refreshes tokens before they expire, caches them in ETS, and injects them into every API request via middleware.

TeslaMate is an open-source data logger for Tesla vehicles that requires secure handling of Tesla API tokens to continuously fetch vehicle data. Understanding how TeslaMate manages Tesla API tokens reveals a sophisticated architecture combining encrypted persistence, automatic token refresh, and request-level authentication injection.

Encrypted Token Storage in the Database

TeslaMate persists tokens using an Ecto schema that ensures raw token strings are never stored in plain text.

The Tokens Schema

In lib/teslamate/auth/tokens.ex, the TeslaMate.Auth.Tokens schema defines the database structure for token storage. The schema declares two encrypted fields:

  • :access – the current access token
  • :refresh – the refresh token used to obtain new access tokens

Both fields are wrapped with Encrypted.Binary, which transparently encrypts the data before it reaches the PostgreSQL database. This ensures that even if the database is compromised, the tokens remain encrypted at rest.

Authentication Context Module

The TeslaMate.Auth module in lib/teslamate/auth.ex serves as the single source of truth for all token operations, providing a clean API for the rest of the application to interact with stored credentials.

CRUD Operations

The module exposes three primary functions:

  • get_tokens/0 – Reads the sole row from the tokens table. Returns the token struct or nil if no tokens exist.
  • save/1 – Receives a map in the format %{token: access, refresh_token: refresh} (produced after OAuth sign-in or refresh) and either inserts a new row or updates the existing one via an upsert operation.
  • delete_tokens/0 – Clears the entire tokens table, effectively signing the user out by removing all stored credentials.

Runtime Token Management with GenServer

The TeslaMate.Api module in lib/teslamate/api.ex implements a long-running GenServer that bridges the gap between persisted storage and active API usage.

Initialization and Caching

When the GenServer starts, the init/1 callback performs the following sequence:

  1. Loads any persisted tokens via Auth.get_tokens/0
  2. If tokens exist, creates a %TeslaApi.Auth{} struct
  3. Immediately refreshes the tokens to ensure validity
  4. Stores the refreshed pair back to the database via Auth.save/1
  5. Caches the authentication struct in an ETS table using :ets.insert(name, auth: auth)

This ETS cache eliminates database queries for every API call, reducing latency and database load.

Token Retrieval and Unauthorized Handling

When making API requests (such as list_vehicles/1), the process calls fetch_auth/1 to retrieve the cached %TeslaApi.Auth{} struct. If the Tesla API returns an unauthorized error, the GenServer automatically triggers a token refresh using Auth.refresh/1 and retries the request without user intervention.

Automatic Token Refresh Scheduling

TeslaMate implements a proactive refresh strategy to prevent token expiration from interrupting data logging.

Refresh Logic

The helper function refresh_tokens/1 in TeslaMate.Api delegates to TeslaApi.Auth.refresh/1 (implemented in lib/tesla_api/auth/refresh.ex). This function calls Tesla's OAuth endpoint to exchange the current refresh token for a new access token.

Scheduling Strategy

After a successful refresh, schedule_refresh/2 calculates the next refresh time as approximately 75% of the expires_in value returned by the OAuth response. It then registers a :refresh_auth message using Process.send_after/3, ensuring the token is refreshed well before it actually expires. The new tokens are immediately persisted via Auth.save/1 and updated in the ETS cache.

Request-Level Authentication Injection

Every outbound HTTP request to the Tesla API passes through middleware that handles authentication headers automatically.

TokenAuth Middleware

In lib/tesla_api/middleware/token_auth.ex, the TeslaApi.Middleware.TokenAuth middleware inspects the %Tesla.Env{} struct. When the environment contains an :access_token field, the middleware injects the header:


Authorization: Bearer <token>

The TeslaApi client modules (such as Vehicle and Stream) receive the %TeslaApi.Auth{} struct containing the current access token, ensuring all API calls are properly authenticated without manual header management.

Practical Code Examples

The following examples demonstrate how to interact with TeslaMate's token management system from Elixir code:


# Sign in and initialize the token lifecycle (normally handled internally)

TeslaMate.Api.sign_in(name, %TeslaMate.Auth.Tokens{access: at, refresh: rt})

# Manually fetch the current access token from ETS cache

case :ets.lookup(TeslaMate.Api, :auth) do
  [auth: %TeslaApi.Auth{token: access}] -> 
    IO.puts("Current access token: #{access}")
  [] -> 
    IO.puts("Not signed in")
end

# Force a token refresh (rarely needed - internal logic handles this)

{:ok, refreshed} = TeslaApi.Auth.refresh(%TeslaApi.Auth{token: old_at, refresh_token: old_rt})
:ok = TeslaMate.Auth.save(refreshed)

# Sign out and clear all stored tokens

:ok = TeslaMate.Api.sign_out()

Summary

  • Encrypted Storage: Tokens are stored in lib/teslamate/auth/tokens.ex using the TeslaMate.Auth.Tokens schema with Encrypted.Binary fields to ensure database-level encryption.
  • Centralized Context: The TeslaMate.Auth module in lib/teslamate/auth.ex provides get_tokens/0, save/1, and delete_tokens/0 for all token CRUD operations.
  • GenServer Architecture: TeslaMate.Api in lib/teslamate/api.ex manages token lifecycle as a GenServer, caching credentials in ETS and handling automatic refresh on unauthorized errors.
  • Proactive Refresh: Tokens are refreshed at 75% of their lifetime via schedule_refresh/2, using TeslaApi.Auth.refresh/1 from lib/tesla_api/auth/refresh.ex.
  • Middleware Injection: TeslaApi.Middleware.TokenAuth in lib/tesla_api/middleware/token_auth.ex automatically adds Bearer tokens to every request.

Frequently Asked Questions

How are Tesla API tokens encrypted in TeslaMate?

TeslaMate encrypts tokens at the database level using the Encrypted.Binary Ecto type defined in the TeslaMate.Auth.Tokens schema. Both the access and refresh token fields are wrapped with this type, ensuring that raw token strings are encrypted before being written to the PostgreSQL database and only decrypted when read back into the application.

What happens when a Tesla API token expires?

When a token expires or returns an unauthorized error, the TeslaMate.Api GenServer automatically calls TeslaApi.Auth.refresh/1 to exchange the refresh token for a new access token. This happens transparently during the request lifecycle, and the application retries the failed request with the new token without user intervention.

Can I manually refresh tokens in TeslaMate?

While TeslaMate handles refresh automatically, you can manually trigger a refresh by calling TeslaApi.Auth.refresh/1 with a %TeslaApi.Auth{} struct containing the current tokens, then persisting the result via TeslaMate.Auth.save/1. However, this is rarely necessary as the schedule_refresh/2 function ensures tokens are refreshed preemptively at 75% of their expiration time.

Where are tokens cached during runtime?

Tokens are cached in an ETS (Erlang Term Storage) table managed by the TeslaMate.Api GenServer. When the process starts, it loads tokens from the database, refreshes them, and inserts them into ETS using :ets.insert(name, auth: auth). Subsequent API requests retrieve the cached %TeslaApi.Auth{} struct from ETS rather than querying the database, significantly improving performance.

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 →