Skip to main content
Loom Core docs

Tracking

Mobile Companion Auth Bootstrap

This document consolidates the auth bootstrap decision, flow descriptions, deployment mode comparison, threat model, and v1 implementation status for the Loom Companion mobile app.

Status: MBL-1 delivered (2026-02-23). Decision gate closed.

Tracking

Decision Summary

Date: 2026-02-19

The v1 default bootstrap mode is native OAuth authorization code + PKCE using an external browser/system auth session. Device-code pairing is the fallback path, used only when direct browser-mediated auth is not practical for a given connection profile.

Key constraints:

  • Flow selection must be explicit per profile/policy. Implicit or silent fallback is not allowed.
  • UI/profile flows must display which bootstrap mode is active and why fallback is being used.
  • Full OAuth 2.1 token lifecycle (PKCE + refresh rotation) is deferred to M2.

Rationale:

  • RFC 8252 and OAuth 2.1 security guidance (RFC 9700) align on browser-mediated native auth with PKCE as the safest default.
  • A constrained-input fallback is required for remote operator scenarios where a full browser auth flow is impractical.
  • Explicit fallback selection reduces phishing and downgrade risk from accidental flow switching.

Alternatives considered:

  • Device-code-first default for all profiles.
  • Hybrid auto-failover without explicit profile selection.
  • Deferring pairing fallback until post-v1.

Source: .loom/40-decisions.md:3-30

Bootstrap Flows

Default: OAuth Authorization Code + PKCE

┌─────────┐    ┌──────────────┐    ┌────────────┐    ┌───────────┐
│  Mobile  │───>│  System      │───>│  Auth      │───>│  Loom     │
│  App     │    │  Browser     │    │  Server    │    │  Backend  │
└─────────┘    └──────────────┘    └────────────┘    └───────────┘
     │                │                    │                │
     │  1. Generate PKCE verifier+challenge               │
     │  2. Open system browser ──────────>│                │
     │                │  3. User authenticates             │
     │                │  4. Auth code redirect ──>│        │
     │  5. Receive auth code via redirect │                │
     │  6. Exchange code + verifier ──────────────────────>│
     │  7. Receive access token + refresh token <──────────│
     │  8. API calls with bearer token ───────────────────>│

Properties:

  • Uses external user-agent (system browser or ASWebAuthenticationSession on iOS).
  • Mitigates embedded-webview credential capture.
  • Short-lived access tokens plus rotating refresh tokens (M2).
  • PKCE prevents authorization code interception.

Fallback: Device-Code Pairing

┌─────────┐    ┌───────────┐    ┌──────────────┐
│  Mobile  │───>│  Loom     │    │  Operator    │
│  App     │    │  Backend  │    │  (Desktop)   │
└─────────┘    └───────────┘    └──────────────┘
     │                │                   │
     │  1. Request device code ──>│       │
     │  2. Receive user_code + device_code│
     │  3. Display user_code      │       │
     │  4. Poll for token ──────>│        │
     │                │    5. Operator enters code ──>│
     │                │    6. Operator approves       │
     │  7. Receive access token <─│       │
     │  8. API calls with bearer  │       │

Properties:

  • Used only when browser-mediated auth is impractical (constrained remote operator workflows).
  • Must be explicitly selected by profile/policy — never auto-selected.
  • Requires hardening controls (see Fallback Hardening below).

LAN vs Gateway Mode: Bootstrap Comparison

Both connectivity modes use the same auth bootstrap flows. The differences are in transport and trust posture.

AspectLAN ModeGateway Mode
NetworkTrusted local/private networkRemote/untrusted network
TransportTLS strongly recommended; HTTP allowed for local debugTLS mandatory (HTTPS only)
Auth flowSame OAuth+PKCE default / device-code fallbackSame OAuth+PKCE default / device-code fallback
Token validationBearer token, constant-time comparisonBearer token, constant-time comparison
Endpoint permissionsSame mobile_operator allowlistSame mobile_operator allowlist
Certificate validationValidates when HTTPS is usedStrictly enforced; self-signed rejected
Trust assumptionNetwork is private; auth still requiredZero-trust; no network-level trust
Bootstrap UXProfile shows LAN mode + active auth flowProfile shows Gateway mode + active auth flow

Key points:

  • Gateway mode must not assume LAN trust. All gateway connections enforce TLS and certificate validation.
  • LAN mode still requires authenticated API access; network locality does not bypass auth.
  • The iOS app enforces gateway HTTPS-only at the application layer (ConnectionViewModel.pair()).
  • Mode selection is per connection profile; operators choose based on their use case.

Source: docs/MOBILE_COMPANION_SECURITY.md:63-78, .loom/40-decisions.md:31-49

Threat Model (Auth-Specific)

Primary threats addressed by the bootstrap design:

ThreatControl
Embedded-webview credential captureExternal user-agent/system auth sessions required (no embedded webviews)
Authorization code interceptionPKCE challenge/verifier binding
Credential replayShort-lived access tokens + rotating refresh tokens (M2)
Pairing brute-forceShort code TTLs, attempt caps, rate limits
Phishing via device-code flowAnti-phishing UX guidance, explicit endpoint confirmation
Silent auth downgradeExplicit profile/policy selection; no implicit fallback
Stolen/compromised device tokenToken revocation, short expiry, device-aware audit logging
MITM on untrusted networksTLS required for gateway mode; strongly recommended for LAN

Source: docs/MOBILE_COMPANION_SECURITY.md:36-50, .loom/40-decisions.md:13-16

Fallback Hardening Requirements

When device-code pairing is used as the fallback bootstrap path:

  1. Short TTLs — Device/user codes must expire quickly (minutes, not hours).
  2. Bounded retry windows — Polling and code entry must have attempt caps.
  3. Brute-force controls — Pairing endpoints must enforce per-actor rate limits and attempt caps.
  4. Anti-phishing UX — Pairing flow must display explicit endpoint confirmation and guidance to help operators verify they are authorizing the correct device.

Source: docs/MOBILE_COMPANION_SECURITY.md:94-98

v1 Implementation Status

The v1 iOS app uses a static bearer token (manual paste at pairing time) as a pragmatic MVP approach:

  • Token is configured via --mobile-operator-token CLI flag or HUD_MOBILE_OPERATOR_TOKEN env var.
  • Validation uses constant-time comparison (crypto/subtle).
  • Token revocation is available at runtime via POST /api/mobile/v1/admin/revoke.
  • Per-actor rate limiting is enforced on all endpoints.
  • Audit logging captures actor, device ID, mode, and action for all mutations.

Full OAuth 2.1 (PKCE + refresh rotation) is deferred to M2, tracked by MBL-2. The static token approach is acceptable for M0/M1 because:

  • Token is manually provisioned (not embedded in app builds).
  • Revocation is immediate and runtime (no restart required).
  • All other security controls (scope checks, audit, rate limits, TLS) are active.

Token Claims (Target for M2)

When full OAuth is implemented, tokens must include:

ClaimPurpose
subActor identity
scope or role claimsEndpoint authorization
device_idDevice/session binding
expToken expiry
mode or aud (optional)Endpoint partitioning by connectivity mode

Source: docs/MOBILE_COMPANION_SECURITY.md:99-104

Acceptance Criteria Verification (MBL-1)

CriterionStatusEvidence
Decision recorded for v1 default modeMet.loom/40-decisions.md:3-30
Decision includes threat model and operational fallback pathMetdocs/MOBILE_COMPANION_SECURITY.md:36-51, 82-98
API and UI contract references selected mode and fallbackMetdocs/MOBILE_COMPANION_API.md:48-60, docs/MOBILE_COMPANION_SECURITY.md:82-87

Checklist items from issue #30:

  • Compare bootstrap options against LAN/gateway deployment modes — See LAN vs Gateway comparison above
  • Document decision + rationale — See Decision Summary above
  • Update API and UX contract docs — MOBILE_COMPANION_API.md:48-60, MOBILE_COMPANION_SECURITY.md:82-98

Cross-References