# How the Tesla API Authentication Flow Handles Token Refresh and MFA in TeslaMate

> Discover how TeslaMate's authentication flow handles token refresh and MFA. Learn about automatic token refresh and MFA challenge resolution for seamless Tesla API access.

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

---

**TeslaMate uses an Elixir GenServer-based authentication system that automatically schedules token refresh at 75% of token expiry and handles MFA challenges by returning a callback that re-attempts sign-in once the user provides the device ID and one-time passcode.**

The TeslaMate data logger for Tesla vehicles maintains persistent access to the Tesla API through robust token management and MFA support. The authentication flow centers on the `TeslaApi.Auth` module, which orchestrates token lifecycle management and challenge-response handling for multi-factor authentication. This implementation ensures uninterrupted vehicle data logging even when access tokens expire or additional security verification is required.

## Token Refresh Mechanism

The token refresh system in [`lib/teslamate/api.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/api.ex) operates as a background process that proactively renews authentication credentials before they expire.

### Initial Token Exchange and Storage

When a user signs in, the system receives a `%Tokens{}` struct containing existing access and refresh tokens. The `TeslaMate.Api.handle_call/3` function delegates to `Auth.refresh/1`, which calls `TeslaApi.Auth.Refresh.refresh/1` to exchange the stored refresh token for a new `%Auth{}` struct. This request uses the `grant_type: "refresh_token"` payload as defined in [`lib/tesla_api/auth/refresh.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/auth/refresh.ex).

Upon successful exchange, the new `%Auth{}` struct (containing access token, refresh token, and expiry timestamp) is stored in an ETS table via `insert_auth/2` and persisted to the database through `TeslaMate.Auth.save/1`.

### Automatic Refresh Scheduling

The `TeslaMate.Api.schedule_refresh/2` function calculates a refresh timeout set to **75% of the token's `expires_in` value**. It registers a timer using `Process.send_after/3` to trigger `:refresh_auth` before the token actually expires.

When the timer fires, the `handle_info(:refresh_auth,…)` callback executes the refresh cycle:
1. Calls `Auth.refresh/1` to obtain new tokens
2. Updates the ETS entry with the fresh credentials
3. Persists the tokens via `TeslaMate.Auth.save/1`
4. Reschedules the next automatic refresh

### Retry Logic on Failure

If a token refresh fails (network error, revoked token, etc.), the system implements exponential backoff with a **five-minute retry interval**. The GenServer continues attempting refresh until successful or until manual intervention occurs, ensuring resilience against temporary API unavailability.

## Multi-Factor Authentication (MFA) Handling

When the Tesla API requires additional verification, TeslaMate implements a callback-based MFA resolution flow rather than failing the authentication attempt.

### Detecting MFA Challenges

During sign-in with email/password credentials, `TeslaMate.Api.handle_call/3` inspects the Tesla API response. If the API returns an MFA challenge, the function returns `{:mfa, devices, callback}` instead of an error, where:
- `devices` is a list of registered MFA devices
- `callback` is a function that accepts device ID and passcode

This detection logic appears in the sign-in handling code within [`lib/teslamate/api.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/api.ex).

### The Callback Pattern

The MFA callback is wrapped (`wrapped_callback`) to maintain the GenServer state across the authentication attempt. When the user supplies the device ID and one-time passcode, the system re-issues the same sign-in call augmented with the MFA data:

```elixir

# The callback wraps the MFA resolution

mfa_callback.(device_id, passcode)

```

This pattern allows the server to suspend the authentication flow, expose the challenge to the user interface, and resume exactly where it left off once the additional factor is provided.

### UI Integration

The LiveView interface in `lib/teslamate_web/live/signin_live/index.html.heex` renders a device selection form and passcode input when it receives the MFA tuple. After the user submits the verification code, the wrapped callback executes, completing the sign-in flow and obtaining a valid access token without requiring the user to re-enter their primary credentials.

## Code Implementation

Below are practical examples of interacting with the TeslaMate authentication system:

```elixir

# Start the TeslaMate API server (usually done by the supervision tree)

{:ok, pid} = TeslaMate.Api.start_link(name: MyTeslaApi)

# Simple sign-in with a previously saved token pair

{:ok, auth} = TeslaMate.Api.sign_in(MyTeslaApi, %TeslaMate.Auth.Tokens{
  access: "old_access_token",
  refresh: "old_refresh_token"
})

```

### Handling MFA Challenges

```elixir

# Sign-in with email/password – the call may return an MFA challenge

case TeslaMate.Api.sign_in(MyTeslaApi, {"user@example.com", "password"}) do
  {:ok, {:mfa, devices, mfa_cb}} ->
    # Choose a device (e.g., first) and ask the user for the passcode

    device_id = hd(devices).id
    passcode = IO.gets("Enter MFA code: ") |> String.trim()
    # Re-invoke the callback with the extra factor

    mfa_cb.(device_id, passcode)

  :ok ->
    IO.puts("Signed in without MFA")
end

```

### Manual Token Refresh

```elixir

# After a successful sign-in the server schedules a refresh automatically.

# You can manually trigger a refresh for testing:

:ok = GenServer.call(MyTeslaApi, :refresh_auth)

# Use the authenticated client to call the Tesla API

{:ok, vehicles} = TeslaMate.Api.list_vehicles(MyTeslaApi)

```

### Key Source Files

| Component | File Path | Description |
|-----------|-----------|-------------|
| Token refresh request | [`lib/tesla_api/auth/refresh.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/auth/refresh.ex) | Builds the `grant_type: "refresh_token"` payload and parses the response into a new `%Auth{}` struct. |
| Core authentication logic | [`lib/tesla_api/auth.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/tesla_api/auth.ex) | Defines the `%Auth{}` struct, token refresh delegate, and helper functions for issuer URL handling. |
| API server orchestration | [`lib/teslamate/api.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/api.ex) | Manages sign-in, token storage in ETS, automatic refresh timer scheduling, and MFA callback wrapping. |
| Token persistence | [`lib/teslamate/auth/tokens.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/auth/tokens.ex) | Ecto schema that stores encrypted access and refresh tokens. |
| MFA UI | `lib/teslamate_web/live/signin_live/index.html.heex` | Renders the form for entering the MFA device and one-time passcode. |

## Summary

- **Automatic refresh**: TeslaMate schedules token refresh at 75% of the `expires_in` duration using `Process.send_after/3`, ensuring tokens never expire during normal operation.
- **Resilient retry**: Failed refreshes trigger automatic retry after five minutes, preventing authentication drops due to transient network issues.
- **ETS + Persistence**: Tokens live in an ETS table for fast access and persist to the database via `TeslaMate.Auth.save/1` for recovery across restarts.
- **MFA callback pattern**: When challenged, the system returns `{:mfa, devices, callback}`, allowing the UI to collect the one-time passcode and resume authentication without restarting the flow.
- **GenServer architecture**: All authentication state lives in `TeslaMate.Api`, providing serialized access and consistent state management for concurrent API requests.

## Frequently Asked Questions

### How does TeslaMate know when to refresh the access token?

TeslaMate calculates the refresh timing based on the `expires_in` field returned during token exchange. According to the implementation in [`lib/teslamate/api.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/api.ex), the system schedules the next refresh at 75% of the token lifetime using `Process.send_after/3`. This provides a buffer to account for network latency and processing time before the token actually expires.

### What happens if the token refresh fails?

If `Auth.refresh/1` returns an error, the `handle_info(:refresh_auth,…)` callback in [`lib/teslamate/api.ex`](https://github.com/teslamate-org/teslamate/blob/main/lib/teslamate/api.ex) schedules a retry after five minutes. This retry mechanism continues until the refresh succeeds, preventing the system from hammering the Tesla API while ensuring eventual recovery from temporary outages or rate limiting.

### Can TeslaMate handle MFA if I use token-based authentication instead of password?

No. The MFA challenge flow only triggers when authenticating with email/password credentials. If you sign in using existing access and refresh tokens (`%TeslaMate.Auth.Tokens{}`), the authentication bypasses the MFA challenge because the Tesla API assumes MFA was already satisfied when those tokens were originally issued.

### Where are the refresh tokens stored securely?

The tokens are stored in two locations: temporarily in an ETS table managed by the `TeslaMate.Api` GenServer for runtime access, and permanently in the database via the `TeslaMate.Auth.Tokens` Ecto schema. The database storage uses encryption for sensitive fields, ensuring that refresh tokens remain secure at rest while remaining accessible for automatic background refreshes.