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 thetokenstable. Returns the token struct ornilif 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 entiretokenstable, 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:
- Loads any persisted tokens via
Auth.get_tokens/0 - If tokens exist, creates a
%TeslaApi.Auth{}struct - Immediately refreshes the tokens to ensure validity
- Stores the refreshed pair back to the database via
Auth.save/1 - 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.exusing theTeslaMate.Auth.Tokensschema withEncrypted.Binaryfields to ensure database-level encryption. - Centralized Context: The
TeslaMate.Authmodule inlib/teslamate/auth.exprovidesget_tokens/0,save/1, anddelete_tokens/0for all token CRUD operations. - GenServer Architecture:
TeslaMate.Apiinlib/teslamate/api.exmanages 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, usingTeslaApi.Auth.refresh/1fromlib/tesla_api/auth/refresh.ex. - Middleware Injection:
TeslaApi.Middleware.TokenAuthinlib/tesla_api/middleware/token_auth.exautomatically 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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →