dart-lang / web_socket_channel

StreamChannel wrappers for WebSockets.
https://pub.dev/packages/web_socket_channel
BSD 3-Clause "New" or "Revised" License
417 stars 109 forks source link

Unable to connect using JWT #378

Open asafgo opened 1 month ago

asafgo commented 1 month ago

Hi,

My Server side code is SignalR and I am unable to connect to it if I am using JWT. if I am not using JWT at server side, I am able to connect.

I am able to connect without any problem with same approach as my following Flutter code when using Postman and C# client code using web sockets.

My Flutter code:

final String token = '<The JWT Token>';
final String wsUrl ='wss://<Web Site URL>/progresshub?access_token==$token';
final wsURI = Uri.parse(wsUrl);
final channel = WebSocketChannel.connect(wsURI);

Also the following code does not return any error and channel object is empty:

try {
      await channel.ready;
    } on SocketException catch (e) {
      debugPrint('ERROR: - SocketException - ${e.message}');
      //print(e.message);
    } on WebSocketChannelException catch (e) {
      debugPrint('ERROR: - SocketException - ${e.message}');
    }

chanel empty

For some reason I am able to add sink's like so:

 channel.sink.add('{"protocol":"json","version":1}');

    channel.sink
        .add('{"type":1,"target":"AssociateJob","arguments":["$jobID"]}');

Error will raise without any location where it is when trying to listen:

channel.stream.listen(
      (message) {

But onError: (error) => debugPrint('Error: ' + error) is not triggered.

Thanks for any help, AG

leeyisoft commented 1 month ago

It is normal for me to put the token in headers, you can refer to my code: https://github.com/imboy-pub/imboy-flutter/blob/main/lib/service/websocket.dart

      _wsChannel = IOWebSocketChannel.connect(
        url!,
        headers: headers,
        pingInterval: Duration(milliseconds: _heartTimes),
        protocols: protocols,
      );
asafgo commented 1 month ago

I tired to following but still not working

 final headers = {
      HttpHeaders.authorizationHeader: 'Bearer $token',
    };
    final channel = IOWebSocketChannel.connect(
        'wss://<my url>/progresshub',
        headers: headers);
leeyisoft commented 1 month ago

我尝试遵循但仍然无效

 final headers = {
      HttpHeaders.authorizationHeader: 'Bearer $token',
    };
    final channel = IOWebSocketChannel.connect(
        'wss://<my url>/progresshub',
        headers: headers);

Then the problem should be on the server side, please check the server side code

asafgo commented 1 month ago

I don't think the problem is with server side as it work without any problem with Postman and C# client code using web sockets.

leeyisoft commented 1 month ago

It should be that your postman has some special configuration that your dart code doesn't have. Pay attention to the content-type setting What I give below is erlang code, but the principle should be the same as C#, is dealing with http protocol, I guess your C# did not accept the parameters submitted by dart Print some logs when you accept the parameters again to see if the parameters submitted by the dart code are the expected values

% -spec post_params(Req::cowboy_req:req()) -> proplists().
% imboy_req:post_params(Req0),
% PostVals = imboy_req:post_params(Req0),
post_params(Req) ->
    ContentType = cowboy_req:parse_header(<<"content-type">>, Req),
    % ?LOG([ContentType]),
    % imboy_log:info(io_lib:format("ContentType: ~p ContentType_End~n", [ContentType])),
    % ?LOG(Method = cowboy_req:method(Req)),
    case ContentType of
        % {<<"text">>,<<"plain">>, [{<<"charset">>,<<"utf-8">>}]} ->
        % {<<"text">>,<<"plain">>, _} ->
        %     [];
        {<<"application">>, <<"x-www-form-urlencoded">>, _} ->
            {ok, Params, _Req} = cowboy_req:read_urlencoded_body(Req, #{length => 640000000, period => 50000}),
            % imboy_log:info(io_lib:format("Params: ~p Params_End~n", [Params])),
            Params;
        {<<"application">>, <<"json">>, _} ->
            {ok, PostVals, _Req} = cowboy_req:read_body(Req),
            % ?LOG(PostVals),
            % Params = jsone:decode(PostVals, [{object_format, proplist}]),
            % ?LOG(Params),
            % Params
            jsone:decode(PostVals, [{object_format, proplist}]);
        _ ->
            imboy_log:error(io_lib:format("imboy_req:post_params error: ContentType ~p; ~p ~n", [ContentType, Req])),
            []
    end.
asafgo commented 1 month ago

Using this code does work

final headers = {
      HttpHeaders.authorizationHeader: 'Bearer $token',
    };

final channel = IOWebSocketChannel.connect(wsUrl, headers: headers);

The code that cause the problem and confusion is the following that the Error is not capture in try..catch block for using await channel.sink.close(status.goingAway); but no error if using await channel.sink.close();

  1. Why await channel.sink.close(status.goingAway); cause error?
  2. What try..catch block does not capture any error?
try {
      await channel.sink.close(status.goingAway);
    } catch (e) {
      print('Error: $e');
    }
leeyisoft commented 1 month ago

Using this code does work

final headers = {
      HttpHeaders.authorizationHeader: 'Bearer $token',
    };

final channel = IOWebSocketChannel.connect(wsUrl, headers: headers);

The code that cause the problem and confusion is the following that the Error is not capture in try..catch block for using await channel.sink.close(status.goingAway); but no error if using await channel.sink.close();

  1. Why await channel.sink.close(status.goingAway); cause error?
  2. What try..catch block does not capture any error?
try {
      await channel.sink.close(status.goingAway);
    } catch (e) {
      print('Error: $e');
    }

I observe the following code, what is the value of your status.goingAway?

0 checkCloseCode (package:web_socket/src/utils.dart:10:5)

/// Throw if the given close code is not valid.
void checkCloseCode(int? code) {
  if (code != null && code != 1000 && !(code >= 3000 && code <= 4999)) {
    throw ArgumentError('Invalid argument: $code, close code must be 1000 or '
        'in the range 3000-4999');
  }
}
asafgo commented 1 month ago

status.dart from web_socket_channel give the option of 1001 that is const goingAway = 1001; so I don't understand if utils.dart allow only 1000 or range of 3000-4999 why status.dart give the option of status 1001 to status 1015?