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

> Discover how TeslaMate securely manages Tesla API tokens with encrypted storage and automatic refreshes. Learn about token lifecycle management for seamless API integration.

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

---

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

```elixir

# 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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/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`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/auth/refresh.ex).
- **Middleware Injection**: `TeslaApi.Middleware.TokenAuth` in [`lib/tesla_api/middleware/token_auth.ex`](https://github.com/teslamate-org/teslamate/blob/main/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.