Skip to main content

Device Tokens

Overview

Device tokens are the authentication mechanism for edge devices participating in federated learning. Unlike user sessions (which rely on browser cookies or OAuth flows), device tokens are designed for headless, long-running processes on hardware that may be intermittently connected and physically accessible to untrusted parties.

The core principle: no long-lived secret should ever be stored on a device. Instead, devices receive short-lived access tokens that are transparently refreshed by the SDK. If a device is compromised, the blast radius is limited to the remaining access token lifetime, and the admin can revoke the refresh token to permanently cut off access.

This model is critical for federated learning because:

  • Devices hold training data that must stay private. A compromised token should not grant access to other devices' data or models.
  • Training rounds require authenticated participation. Expired or revoked tokens prevent stale or malicious devices from submitting updates.
  • Fleet management at scale (thousands of devices) requires automated credential rotation, not manual key distribution.

Flow

  1. Backend obtains a bootstrap bearer token (admin-controlled)
  2. SDK exchanges bootstrap token at /api/v1/device-auth/bootstrap
  3. SDK stores token state securely:
    • iOS: Keychain
    • Android: Keystore-backed encrypted storage
    • Python: system keyring
  4. SDK uses access token for device calls
  5. SDK refreshes via /api/v1/device-auth/refresh
  6. SDK revokes via /api/v1/device-auth/revoke

Detailed Bootstrap Sequence

The bootstrap flow involves three parties: your backend service (trusted), the Octomil API, and the device.

Your Backend                   Octomil API                    Device
| | |
|-- POST /device-auth/token -->| |
| (org API key + device_id) | |
|<-- bootstrap_token (short-lived) ---| |
| | |
|-- deliver bootstrap_token ---|-------------------------->|
| (app provisioning / MDM) | |
| | |
| |<-- POST /device-auth/bootstrap
| | (bootstrap_token) |
| | |
| |-- access_token (short-lived) ->|
| | refresh_token (long-lived) |
| | |
| |<-- API calls with ------->|
| | access_token |

The bootstrap token is single-use. Once exchanged, it cannot be used again. If the exchange fails (network error, invalid token), your backend must issue a new bootstrap token.

Token Storage Security by Platform

Secure storage is non-negotiable. A token stored in plaintext on disk is equivalent to no authentication.

iOS: Keychain Services

The iOS SDK stores tokens in the device Keychain using platform-specific secure storage. Tokens are bound to the specific device hardware, are available after the first device unlock, and are not accessible from backups or other devices. If the app is deleted, Keychain items persist and are reclaimed on reinstall (configurable).

Android: Keystore-Backed Encrypted Storage

The Android SDK stores tokens using platform-specific secure storage backed by the Android Keystore. Encryption keys are stored in the hardware-backed Keystore (TEE or StrongBox where available). Tokens are encrypted at rest and decrypted only in the Keystore's secure environment. On devices with biometric hardware, you can optionally require biometric authentication to access tokens.

Python: System Keyring

The Python SDK stores tokens using the platform's native credential store (Keychain on macOS, Secret Service on Linux, Credential Locker on Windows). For headless Linux servers without a desktop environment, the SDK falls back to an encrypted file store with a machine-bound key.

TTL and Rotation

  • Access tokens are short-lived
  • Refresh token enables session continuation without embedding long-lived org keys
  • Revoke on device logout, compromise, or deprovision

Refresh Strategy and Timing

The SDK manages token refresh automatically. The refresh logic follows these rules:

  1. Proactive refresh: The SDK refreshes the access token before the access token expires. This avoids failed API calls due to token expiry during a request.
  2. Reactive refresh: If an API call returns 401 Unauthorized, the SDK attempts one refresh before surfacing the error to the caller.
  3. Refresh token rotation: Each successful refresh returns a new refresh token. The old refresh token is invalidated. This limits the window of exposure if a refresh token is intercepted.
  4. Backoff on failure: If a refresh fails (network error, server error), the SDK retries with exponential backoff.
# Python SDK - automatic refresh is handled internally
from octomil import DeviceClient

client = DeviceClient(bootstrap_token="ey...")
# Access token refresh happens transparently
result = client.submit_training_update(weights=updated_weights)

Revocation Scenarios

ScenarioWho RevokesHowEffect
Device decommissionedAdmin (dashboard or API)DELETE /api/v1/devices/{device_id}All tokens for device invalidated
Suspected compromiseAdminPOST /api/v1/device-auth/revoke with device_idRefresh token invalidated; access token valid until expiry
User deprovisioned via SCIMAutomatic (SCIM webhook)All devices registered by user have tokens revokedImmediate access loss
App uninstallSDK (on cleanup)POST /api/v1/device-auth/revokeGraceful cleanup
Bulk fleet revocationAdmin (API)POST /api/v1/device-auth/revoke-batchBatch revocation for device group

Offline behavior

  • Device event/training updates are queued locally where supported
  • SDK refresh attempts should retry on reconnect; keep last valid access token until expiry
  • If token expires while offline, device operations requiring auth fail until refresh succeeds

Offline Queue Behavior

When a device loses connectivity:

  1. Training updates are queued in local storage (SQLite on mobile, file-based on Python).
  2. Updates are queued locally with configurable limits.
  3. On reconnect, the SDK first refreshes the token (if needed), then drains the queue in order.
  4. If the token is irrecoverably expired (refresh token also expired), the SDK emits a token_expired callback so your app can trigger re-bootstrap.

Error Handling

Common Error Responses

HTTP StatusError CodeMeaningSDK Behavior
401token_expiredAccess token has expiredAuto-refresh, retry once
401token_revokedToken has been explicitly revokedEmit callback, require re-bootstrap
401bootstrap_usedBootstrap token already exchangedRequest new bootstrap from backend
403device_inactiveDevice has been deactivatedEmit callback, stop operations
429rate_limitedToo many requestsBackoff per Retry-After header
503service_unavailableServer temporarily unavailableRetry with exponential backoff

Handling Network Failures During Bootstrap

If the bootstrap exchange fails due to a network error, the bootstrap token may or may not have been consumed server-side. The SDK handles this by:

  1. Retrying the exchange with backoff.
  2. If all retries fail, reporting the failure to the caller.
  3. The caller (your backend) should issue a new bootstrap token. The server idempotently handles duplicate bootstrap attempts for the same device within the token's TTL.

Monitoring Token Health

Track device authentication health in the Monitoring Dashboard. Key metrics include bootstrap success rate, refresh success rate, revocation events, and active token count per organization.

Set alerts for:

  • Elevated bootstrap failure rate (indicates provisioning issues)
  • Elevated refresh failure rate (indicates token or server health issues)
  • Active token count dropping sharply (indicates mass revocation or outage)

Why this model

  • Limits blast radius of leaked client-side credentials
  • Supports explicit revocation and auditability
  • Separates backend trust boundary from untrusted device runtime