Getting Connected: Authentication Flow

Let’s follow a user named Alex as they open the MentraOS mobile app and connect their smart glasses to the cloud. This journey shows how authentication works and how WebSocket connections are established.

Step 1: Mobile App Login

When Alex opens the MentraOS app on their phone:
  1. User Authentication
    • Alex logs in with their email/password
    • The app authenticates with our auth service
    • App receives a coreToken (JWT) that identifies Alex
  2. What’s in the coreToken?
    {
      "sub": "alex@example.com",  // User's email
      "exp": 1234567890,          // Expiration time
      "iat": 1234567800           // Issued at time
    }
    

Step 2: Glasses Connect via Phone

Once authenticated, the mobile app:
  1. Establishes Bluetooth connection with Alex’s smart glasses
  2. Opens WebSocket to cloud at wss://cloud.mentraos.com/glasses-ws
  3. Sends the coreToken in the Authorization header:
    Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
    

Step 3: WebSocket Handshake

Now the magic happens in our cloud:

1. Connection Arrives at WebSocketService

// In WebSocketService.ts
app.ws('/glasses-ws', (ws, req) => {
  // Extract and verify the JWT token
  const token = req.headers.authorization?.split(' ')[1];
  const decoded = jwt.verify(token, JWT_SECRET);
  const userEmail = decoded.sub;
  
  // Hand off to GlassesWebSocketService
  glassesWebSocketService.handleConnection(ws, userEmail);
});

2. GlassesWebSocketService Takes Over

// In GlassesWebSocketService.ts
handleConnection(ws: WebSocket, userEmail: string) {
  // Send connection acknowledgment
  ws.send(JSON.stringify({
    type: 'CONNECTION_ACK',
    sessionId: generateSessionId()
  }));
  
  // Create or retrieve UserSession
  let session = SessionStorage.getSession(userEmail);
  if (!session) {
    session = new UserSession(userEmail, ws);
    SessionStorage.addSession(session);
  } else {
    // Reconnection - update WebSocket
    session.updateWebSocket(ws);
  }
}

3. The First Message Exchange

Mobile app must send CONNECTION_INIT within 30 seconds:
// From mobile app (defined in glasses-to-cloud.ts:15-19)
{
  type: "CONNECTION_INIT",
  userId?: "alex@example.com",  // Optional user ID
  coreToken?: "eyJhbGci..."     // Optional auth token
}
Cloud responds with CONNECTION_ACK:
// From cloud (defined in cloud-to-glasses.ts:19-23)
// Sent from websocket-glasses.service.ts:558-566
{
  type: "CONNECTION_ACK",
  sessionId: "alex-123456",      // Unique session ID
  userSession: {                 // Partial<UserSession> from transformUserSessionForClient
    userId: "alex@example.com",
    startTime: "2024-01-01T00:00:00Z",
    activeAppSessions: ["com.app1", "com.app2"],
    loadingApps: [],
    appSubscriptions: {
      "com.app1": ["audio", "transcription"],
      "com.app2": ["notification"]
    },
    requiresAudio: true,
    minimumTranscriptionLanguages: ["en-US"],
    isTranscribing: true
  },
  timestamp: "2024-01-01T00:00:00Z"
}

Step 4: Heartbeat Keeps Connection Alive

Every 10 seconds, the cloud sends a ping to check if the connection is alive:
// In UserSession.ts
private startHeartbeat() {
  this.heartbeatInterval = setInterval(() => {
    if (this.websocket.readyState === WebSocket.OPEN) {
      this.websocket.ping();
    }
  }, 10000);
}
If the mobile app doesn’t respond to pings, the connection is considered dead and cleaned up.

Step 5: Handling Reconnections

What happens when Alex’s phone loses connection?
  1. Grace Period (30 seconds)
    • UserSession remains in memory
    • Apps stay in “resurrecting” state
    • No data is lost
  2. Reconnection Within Grace Period
    • Same coreToken is used
    • UserSession is retrieved from SessionStorage
    • WebSocket is updated with new connection
    • Apps are notified of reconnection
    • Everything continues seamlessly
  3. Reconnection After Grace Period
    • New UserSession is created
    • Apps must be restarted
    • Previous state is lost

Security Considerations

  1. JWT Validation
    • All tokens are verified for signature
    • Expired tokens are rejected
    • Invalid tokens result in CONNECTION_ERROR
  2. User Isolation
    • Each UserSession is keyed by email
    • No cross-user data access
    • Sessions are completely isolated
  3. Connection Limits
    • One active glasses connection per user
    • New connections replace old ones
    • Prevents connection flooding

Common Connection Errors

// Connection rejected - invalid token
{
  type: "CONNECTION_ERROR",
  error: "Invalid authentication token"
}

// Connection rejected - expired token
{
  type: "CONNECTION_ERROR", 
  error: "Token expired"
}

// Connection timeout - no CONNECTION_INIT
{
  type: "CONNECTION_ERROR",
  error: "Connection initialization timeout"
}

What’s Next?

Now that Alex is connected, the cloud creates a UserSession for them. This session is their personal command center in the cloud. Let’s explore UserSessions next!