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

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 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.

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.

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:


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


# 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


# 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


# 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 Builds the grant_type: "refresh_token" payload and parses the response into a new %Auth{} struct.
Core authentication logic 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 Manages sign-in, token storage in ETS, automatic refresh timer scheduling, and MFA callback wrapping.
Token persistence 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, 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 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.

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 →