0

I try to send HTTP GET request to a server with a "client certificate" enabled in my Flutter app. The server allows client certificates because some of the clients (Desktop apps) use certificates for authorization with this server.

Expected results: HTTP response

Actual results: The GET request receives in the server but the Dart's HTTP GET method throws a ClientException with an empty message before a response is received.

Server configuration and details: OS: Windows Host: IIS HTTP version: 1.1 Minimum TLS version: 1.2 Runtime: ASP .NET Core 5

If I disable "client certificate" authorization on the server everything works fine. But this is not a good solution for me.

BTW: I manage to make a request and get a response via other in other platforms to the same endpoint in this server without any problem! I try C# console app, Xamrin Forms app and Postman and this issue exists only in Flutter!

Update: I still don't have a solution for this one. At your request I attached my code snipped, it's a simple HTTP service. Nothing fancy. And the error I get is ClientException without a message.

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:http/http.dart' as http;
import 'package:retry/retry.dart';

import '../models/app_exception.dart';
import 'logs_service.dart';

class HttpService {
  final _timeoutDuration = Duration(minutes: 1);
  final _retryOptions = RetryOptions(maxAttempts: 3);

  final _headers = {
    HttpHeaders.acceptHeader: 'application/json; charset=UTF-8',
    HttpHeaders.pragmaHeader: 'no-cache',
    HttpHeaders.contentTypeHeader: 'application/json; charset=UTF-8',
    HttpHeaders.acceptCharsetHeader: 'UTF-8',
  };

  Future<dynamic> get({
    required String url,
    String? authorizationToken,
    Map<String, String>? args,
    String? basicAuthUsername,
    String? basicAuthPassword,
  }) {
    return _retryOptions.retry(
      () => _get(
        url,
        authorizationToken,
        args,
        basicAuthUsername,
        basicAuthPassword,
      ).timeout(_timeoutDuration),
      retryIf: (e) => e is NetworkException,
    );
  }

  Future<dynamic> _get(
    String baseUrl,
    String? authorizationToken,
    Map<String, String>? args,
    String? basicAuthUsername,
    String? basicAuthPassword,
  ) async {
    try {
      var responseJson;

      final headers = Map<String, String>.from(_headers);

      if (authorizationToken != null) {
        headers[HttpHeaders.authorizationHeader] = authorizationToken;
      }
      if (basicAuthUsername != null && basicAuthPassword != null) {
        headers[HttpHeaders.authorizationHeader] =
            _getBasicAuthStr(basicAuthUsername, basicAuthPassword);
      }

      final argsStr = args.convertToQueryString;

      final url = Uri.parse('$baseUrl$argsStr');

      try {
        final response = await http.get(
          url,
          headers: headers,
        );

        logger.info(
            text:
                'GET | url: $url | response status code: ${response.statusCode}');

        responseJson = _returnResponse(response.body, response.statusCode);
      } on SocketException {
        throw NetworkException('No Internet connection');
      }
      return responseJson;
    } catch (e) {
      logger.error(error: e, text: 'GET | error | url: $baseUrl');
      throw e;
    }
  }

  
  dynamic _returnResponse(String responseBody, int statusCode) {
    if (statusCode >= 200 && statusCode < 300) {
      final responseJson = responseBody.isNotEmpty
          ? json.decode(responseBody)
          : Map<String, dynamic>();
      return responseJson;
    }

    switch (statusCode) {
      case 400:
        throw BadRequestException(responseBody);
      case 401:
        throw UnauthorizedException(responseBody);
      case 403:
        switch (responseBody) {
          case '\"DeviceUnregistered\"':
            throw DeviceUnregisteredException(responseBody);
          case '\"NoAppLicense\"':
            throw NoAppLicenseException(responseBody);
          case '\"InvitationNotFound\"':
            throw InvitationNotFoundException(responseBody);
          case '\"InvitationExpired\"':
            throw InvitationExpiredException(responseBody);
          case '\"DeviceAlreadyRegistered\"':
            throw DeviceAlreadyRegisteredException(responseBody);
        }
        throw ForbiddenException(responseBody);
      case 404:
        throw NotFoundException(responseBody);
      case 500:
      case 501:
      case 502:
      case 503:
        throw ServerErrorException(
            'status code: $statusCode | body: $responseBody');
      default:
        throw GeneralHttpErrorException(
            'Error occurred while communication with server | Status code: $statusCode | body: $responseBody');
    }
  }

  String _getBasicAuthStr(String userName, String password) =>
      'Basic ' + base64Encode(utf8.encode('$userName:$password'));
}

extension StringMapeExtension on Map<String, String?>? {
  String get convertToQueryString {
    String query = '';
    if (this == null) {
      return query;
    }
    for (MapEntry e in this!.entries) {
      query += '&${e.key}=${e.value}';
    }
    return ('?${query.substring(1)}');
  }
}    
N.S
  • 29
  • 4
  • If you are trying on android, make sure that you have added internet permission in the manifest file. – Elias Andualem Jun 26 '21 at 19:28
  • @EliasAndualem OP already added the permission. You can infer that from his/her affirmation "If I disable "client certificate" authorization on the server everything works fine. But this is not a good solution for me." –  Jun 27 '21 at 12:56
  • 1
    could you provide the actual exception or the message you got from android studio console? and please include your code snippet too. that will be great to understand the actual problem here. – Saiful Islam Jul 01 '21 at 06:18
  • Take a look at https://stackoverflow.com/questions/25388750/dart-https-request-with-ssl-certificate-please#33280511 – msbit Jul 01 '21 at 13:27
  • 1
    I agree with @Saiful Islam. How do you expect people to debug this without posting your errors or sample code? – eyoeldefare Jul 02 '21 at 05:35
  • Hi, @eyoeldefare Sorry for the late response. I add my code snippet for the origin question. the point is - I use this service ןn many places in the app without any problem. The above problem only appears when I try to get the response from the particular server I described. – N.S Jul 28 '21 at 12:20
  • @msbit, thanks. but I didn't want to add a client certificate from the app. I want to do a basic authentication. The problem occurs when the server allows authentication with a client certificate even if it does not require one. – N.S Jul 28 '21 at 12:25
  • `I try C# console app, Xamrin Forms app and Postman and this issue exists only in Flutter!`, to confirm, you are trying these implementations without a client side certificate? – msbit Jul 28 '21 at 13:23
  • hi @msbit, true. without a client-side certificate and with the same server with the "client certificate" enabled. – N.S Jul 31 '21 at 15:48
  • Thanks @N.S. I had a quick look at running a webapp with dotnet core without IIS (`dotnet run`) but there was no issue. Could you add a publicly accessible URL that exhibits this behaviour? – msbit Aug 03 '21 at 11:20
  • Hi @msbit, I can not do that , unfortunately :( – N.S Aug 05 '21 at 19:58
  • Hmm, that makes it hard for anyone to help effectively. It may not necessarily need to be a sensitive endpoint, my suspicion is an interaction between Dart and IIS, so a generic GET (or POST) endpoint on a different webapp might also exhibit the same behaviour and allow some troubleshooting by others. – msbit Aug 06 '21 at 04:05

0 Answers0