How AionUi Provides Remote Browser Access with JWT Authentication and WebSockets

AionUi enables secure remote browser access by running in remote mode with CORS-configured LAN access, JWT-based authentication via HTTP-only cookies and Authorization headers, and WebSocket connections validated on every handshake and heartbeat.

AionUi's WebUI architecture supports both local development and production remote access scenarios. When configured for remote access, the application exposes a secure web interface accessible from any device on the same network, protected by stateless JWT authentication that applies uniformly to both REST API endpoints and real-time WebSocket channels.

Enabling Remote Mode and Network Configuration

AionUi operates in two distinct networking modes controlled by the allowRemote parameter. In local mode (default), the server binds to 127.0.0.1 and accepts connections only from the same machine. When remote mode is activated—either via CLI flag (npm start -- --remote) or programmatically—the server binds to 0.0.0.0 and calculates the host's LAN IP address to facilitate cross-device access.

In src/webserver/index.ts, the startWebServerWithInstance function orchestrates this startup process. It calls SERVER_CONFIG.setServerConfig to persist the remote mode state, invokes getServerIP to determine the public-facing address, and prints both localhost and network URLs to the console. This allows users to connect from browsers running on smartphones, tablets, or other computers within the same local network.

CORS Configuration for Cross-Origin Requests

When allowRemote is enabled, the server automatically configures Cross-Origin Resource Sharing (CORS) to permit browser access from the LAN IP address. The setupCors function in src/webserver/setup.ts delegates to getConfiguredOrigins, which dynamically constructs the origin string http://<lan-ip>:<port> and adds it to the whitelist alongside localhost.

This configuration is critical for modern browser security models. Without explicit CORS headers allowing the LAN origin, browsers would block XHR and fetch requests when accessing the UI from remote devices, even when connected to the same physical network.

JWT Authentication Implementation

The authentication system centers on JSON Web Tokens generated by AuthService.generateToken and returned to clients upon successful login via the /login endpoint in src/webserver/routes/authRoutes.ts. The server transmits the JWT through two parallel mechanisms:

  • HTTP-only cookie: Stored under the name defined by AUTH_CONFIG.COOKIE.NAME, providing automatic token transmission for browser-based requests
  • JSON response body: Available in the token field for programmatic clients that manage authentication state manually

This dual-delivery approach ensures compatibility with both browser-based UI interactions and API consumers. The cookie configuration uses secure flags appropriate for the deployment context, while the JSON payload enables mobile applications and scripts to extract the token for header-based authentication.

Token Extraction and Validation Middleware

All protected routes and WebSocket connections rely on TokenMiddleware.ts for token extraction and validation. The TokenExtractor.extract method inspects incoming requests for the JWT in the following priority order:

  1. Authorization: Bearer <jwt> header
  2. Session cookie (name specified in constants)
  3. Explicit rejection of URL query parameters (security hardening)

For WebSocket connections, the extractWebSocketToken function performs analogous extraction during the handshake phase. The middleware then invokes AuthService.verifyToken for HTTP requests or AuthService.verifyWebSocketToken for socket connections, validating the signature, expiration timestamp, and optionally a per-user jwt_secret. Invalid tokens result in immediate 403 Forbidden responses, either as JSON for API routes or HTML error pages for direct browser access.

WebSocket Connection Management and Security

Real-time communication flows through the WebSocketManager class in src/webserver/websocket/WebSocketManager.ts. The connection lifecycle implements rigorous authentication checks:

Handshake Validation: Clients first obtain a valid token via the /api/ws-token endpoint (which simply echoes the current session JWT), then initiate the WebSocket connection. The server validates the token during the upgrade handshake using the same middleware logic as HTTP routes.

Ongoing Verification: The manager runs periodic checkClients cycles that validate tokens on every heartbeat tick. If a token expires during an active session, the server emits an auth-expired message to the client and terminates the socket connection.

Protocol Compliance: Clients can transmit the JWT via the Authorization header, cookie mechanism, or the Sec-WebSocket-Protocol header during the initial handshake, ensuring compatibility with various WebSocket client implementations.

Token Refresh and Session Continuity

To maintain long-running remote sessions without forcing re-authentication, AionUi provides the /api/auth/refresh endpoint. Clients detect the auth-expired WebSocket message or 403 HTTP responses and call this endpoint with their existing (but expired) token to receive a fresh JWT. This mechanism allows continuous remote monitoring and control without interrupting the user workflow.

Practical Implementation Examples

Starting the Server in Remote Mode


# Enable remote access from any device on the LAN

npm run start -- --remote

Authenticating and Storing the JWT

async function login(username: string, password: string) {
  const resp = await fetch('/login', {
    method: 'POST',
    credentials: 'include',               // cookie will be set automatically
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ username, password })
  });
  const data = await resp.json();
  if (data.success) {
    const jwt = data.token;               // available in body or cookie
    return jwt;
  }
  throw new Error(data.message);
}

Establishing an Authenticated WebSocket Connection

async function openSecureWebSocket() {
  const token = await login('admin', 'password');
  
  const ws = new WebSocket(`ws://${window.location.host}`);
  
  ws.addEventListener('open', () => {
    // Authenticate immediately upon connection
    ws.send(JSON.stringify({ name: 'auth', data: { token } }));
  });
  
  ws.onmessage = (event) => {
    const msg = JSON.parse(event.data);
    if (msg.name === 'auth-expired') {
      console.error('Session expired, refresh required');
    }
  };
}

Refreshing an Expired Token

async function refreshToken(oldToken: string) {
  const resp = await fetch('/api/auth/refresh', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ token: oldToken })
  });
  const { token } = await resp.json();
  return token;
}

Summary

  • Remote mode activates by setting allowRemote = true or using the --remote CLI flag, binding the server to 0.0.0.0 instead of localhost
  • Dynamic CORS in src/webserver/setup.ts automatically whitelists the host's LAN IP address when remote mode is enabled
  • Dual JWT delivery via HTTP-only cookies and JSON response body ensures compatibility with browsers and API clients
  • Unified validation through TokenMiddleware.ts enforces consistent security across REST endpoints and WebSocket connections
  • Continuous verification via WebSocketManager.ts checks token validity on every heartbeat, emitting auth-expired when credentials expire
  • Token refresh via /api/auth/refresh enables seamless session continuity without requiring manual re-login

Frequently Asked Questions

How do I access AionUi from my phone when running on my laptop?

Enable remote mode by starting the server with npm run start -- --remote. The console will display a network URL containing your laptop's LAN IP address (e.g., http://192.168.1.5:3000). Ensure both devices connect to the same Wi-Fi network, then open this URL in your phone's browser. The server automatically configures CORS to accept requests from this origin.

Why does AionUi reject my JWT when passed as a URL parameter?

The TokenExtractor class in src/webserver/auth/middleware/TokenMiddleware.ts explicitly ignores URL query parameters for security reasons. Passing tokens in URLs exposes credentials in browser history and server logs. Instead, transmit the JWT via the Authorization: Bearer header or rely on the HTTP-only cookie set during login.

What happens when my WebSocket token expires during an active session?

The WebSocketManager validates tokens during its periodic heartbeat checks. Upon detecting an expired token, it sends an auth-expired message to the client and immediately closes the connection. Your client application should listen for this message and call the /api/auth/refresh endpoint to obtain a new token before reconnecting.

Can I use the same authentication token for both REST API calls and WebSocket connections?

Yes. AionUi uses identical JWT validation logic for both transport mechanisms. The token obtained from /login or /api/ws-token works interchangeably in HTTP Authorization headers and WebSocket authentication messages. The AuthService.verifyToken and AuthService.verifyWebSocketToken methods apply the same cryptographic verification and expiration checks.

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 →