From 803cea0fe497216fd8b74707232025202bc15af2 Mon Sep 17 00:00:00 2001 From: hailin Date: Mon, 23 Feb 2026 16:43:31 -0800 Subject: [PATCH] fix: pass JWT token in WebSocket connection headers WebSocket connections to /ws/agent were rejected by Kong (401) because the Authorization header was not included. Now reads access_token from secure storage and passes it in the WebSocket upgrade request headers. Co-Authored-By: Claude Opus 4.6 --- it0_app/lib/core/network/websocket_client.dart | 5 ++++- .../chat/data/repositories/chat_repository_impl.dart | 11 ++++++++--- .../chat/presentation/providers/chat_providers.dart | 3 +++ 3 files changed, 15 insertions(+), 4 deletions(-) diff --git a/it0_app/lib/core/network/websocket_client.dart b/it0_app/lib/core/network/websocket_client.dart index c7f7cf3..948ea19 100644 --- a/it0_app/lib/core/network/websocket_client.dart +++ b/it0_app/lib/core/network/websocket_client.dart @@ -31,7 +31,10 @@ class WebSocketClient { final uri = Uri.parse('$baseUrl$path'); try { - _channel = WebSocketChannel.connect(uri); + _channel = WebSocketChannel.connect( + uri, + headers: token != null ? {'Authorization': 'Bearer $token'} : null, + ); _isConnected = true; _reconnectAttempts = 0; diff --git a/it0_app/lib/features/chat/data/repositories/chat_repository_impl.dart b/it0_app/lib/features/chat/data/repositories/chat_repository_impl.dart index 1a4e63d..347aca1 100644 --- a/it0_app/lib/features/chat/data/repositories/chat_repository_impl.dart +++ b/it0_app/lib/features/chat/data/repositories/chat_repository_impl.dart @@ -11,14 +11,17 @@ class ChatRepositoryImpl implements ChatRepository { final ChatRemoteDatasource _remoteDatasource; final ChatLocalDatasource _localDatasource; final WebSocketClient _webSocketClient; + final Future Function() _getAccessToken; ChatRepositoryImpl({ required ChatRemoteDatasource remoteDatasource, required ChatLocalDatasource localDatasource, required WebSocketClient webSocketClient, + required Future Function() getAccessToken, }) : _remoteDatasource = remoteDatasource, _localDatasource = localDatasource, - _webSocketClient = webSocketClient; + _webSocketClient = webSocketClient, + _getAccessToken = getAccessToken; @override Stream sendMessage({ @@ -39,7 +42,8 @@ class ChatRepositoryImpl implements ChatRepository { final taskId = response['taskId'] as String? ?? response['task_id'] as String?; // Connect to the agent WebSocket and subscribe to the session - await _webSocketClient.connect('/ws/agent'); + final token = await _getAccessToken(); + await _webSocketClient.connect('/ws/agent', token: token); _webSocketClient.send({ 'event': 'subscribe_session', 'data': {'sessionId': returnedSessionId, 'taskId': taskId}, @@ -87,7 +91,8 @@ class ChatRepositoryImpl implements ChatRepository { sessionId; final taskId = response['taskId'] as String? ?? response['task_id'] as String?; - await _webSocketClient.connect('/ws/agent'); + final voiceToken = await _getAccessToken(); + await _webSocketClient.connect('/ws/agent', token: voiceToken); _webSocketClient.send({ 'event': 'subscribe_session', 'data': {'sessionId': returnedSessionId, 'taskId': taskId}, diff --git a/it0_app/lib/features/chat/presentation/providers/chat_providers.dart b/it0_app/lib/features/chat/presentation/providers/chat_providers.dart index c27bba0..cc6e2a2 100644 --- a/it0_app/lib/features/chat/presentation/providers/chat_providers.dart +++ b/it0_app/lib/features/chat/presentation/providers/chat_providers.dart @@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import '../../../../core/network/dio_client.dart'; import '../../../../core/network/websocket_client.dart'; +import '../../../auth/data/providers/auth_provider.dart'; import '../../data/datasources/chat_local_datasource.dart'; import '../../data/datasources/chat_remote_datasource.dart'; import '../../data/models/chat_message_model.dart'; @@ -39,12 +40,14 @@ final chatRepositoryProvider = Provider((ref) { final remote = ref.watch(chatRemoteDatasourceProvider); final local = ref.watch(chatLocalDatasourceProvider); final ws = ref.watch(webSocketClientProvider); + final storage = ref.watch(secureStorageProvider); // Use a no-op local datasource if SharedPreferences is not yet ready return ChatRepositoryImpl( remoteDatasource: remote, localDatasource: local ?? _NoOpLocalDatasource(), webSocketClient: ws, + getAccessToken: () => storage.read(key: 'access_token'), ); });