Flutter OAuth request fails when using self-signed certificate

1,260

Solution 1

DART OPTION 1

Looks like Dart has its own root certificates. The preferred option is to avoid writing any security code. Instead in a development environment, configure your self signed host's root certificate as trusted by Dart, according to this guide.

DART OPTION 2

Looks like Dart also supports the C# certificate callback model, where there is a Bad Certificate Callback that you can override. Not sure if you have to subclass HttpClient to achieve this.

/* PSEUDOCODE */
bool callback(X509Certificate cert, String host, int port) {

  // Don't allow any exceptions in production
  if (currentEnvironment == "DEV" && host == "myhost.com") {
    return true;
  }

  // Use system
  return base.callback(cert, host, port)

}

MOBILE OAUTH RECOMMENDATIONS

I see you are trying a few different libraries to solve your SSL trust problem. So I thought I'd point out what I look for in a mobile OAuth library, in line with mobile security standards, where these are the key recommendations:

  • Use Authorization Code Flow (PKCE)
  • Login via the System Browser
  • Prefer HTTPS redirect URLs (claimed HTTPS schemes)

I would at least aim to use the correct flow as above. I'm always a bit wary of new tech stacks and their OAuth libraries, since they often don't implement the recommended behaviour.

The preferred library from a security viewpoint is probably Flutter AppAuth. I have often used AppAuth libraries with self signed certificates, but the AppAuth library comes with these challenges:

  • Login on a System Browser is tricky to make reliable
  • Could be quite a bit more work than your stakeholders want to pay for
  • User experience aspects may be different to what people are used to
  • The Flutter bridge may come with its own problems

APP AUTH RESOURCES OF MINE

When you get some time it may be worth browsing my blog posts and running my Swift / Kotlin code samples, to see if you think any of this behaviour would be useful to you:

Solution 2

Completing some previous answers, I discovered that it's possible to pass an http.Client as a named argument in the oauth2.AuthorizationCodeGrant constructor.

So I made one that hooks a badCertificateCallback where I can implement some rules to ignore certificate validation under some particular circumstances (like calling 10.0.2.2 from an emulator in a dev environment). I think it could go as far as looking at some X509Certificate attributes to make the decision.

  bool _certificateCheck(X509Certificate cert, String host, int port) =>
    host == '10.0.2.2';

  http.Client devEmulatorClient() {
    var ioClient = new HttpClient()
      ..badCertificateCallback = _certificateCheck;

    return new IOClient(ioClient);
  }

  final grant = oauth2.AuthorizationCodeGrant(
      _clientId, _authorizationEndpoint, _tokenEndpoint,
      httpClient: devEmulatorClient());

And later whereas this call use to throw the certificate check exception, it is now accepting my dev self-signed certificate.

  var client =
      await grant.handleAuthorizationResponse(responseUrl.queryParameters);

Required imports:

import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:http/io_client.dart';
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'package:oauth2/oauth2.dart' as oauth2;
Share:
1,260
mmbrian
Author by

mmbrian

Updated on December 21, 2022

Comments

  • mmbrian
    mmbrian over 1 year

    I have a server that supports oauth2.0 with authorization code grant. server uses a self-signed certificate which i have manually installed on my android device. I am using the standard oauth2 library https://pub.dev/packages/oauth2 to perform oauth flow. Now everything works fine until this step

    var client = await grant.handleAuthorizationResponse(responseUrl.queryParameters);
    

    Where client has to make a post request using code fetched during authorization to get an access token. (see example from https://pub.dev/packages/oauth2#authorization-code-grant) here I get a

    HandshakeException: Handshake error in client (OS Error: 
    E/flutter (11483):  CERTIFICATE_VERIFY_FAILED: self signed certificate in certificate
    

    Now I already know how to allow my certificate or completely bypass certificate check using a HttpClient object. problem is, HttpClient is part of the library dart._http which is under http.dart, but the Client object oauth2.dart uses is from http library under client.dart. even though they both seem to be http clients and support post methods only the former supports a custom SecurityContext. and there's no way apparently I can cast one into the other. I have a two part question:

    1. has anyone had a similar experience with this OAuth2.0 library or know if I can make it work with my self-signed certificate at all?
    2. my latest idea is to create a custom client class extending http.BaseClient. since I noticed OAuth2.0 only uses post method from the client object I am thinking of overriding this method and use a HttpClient object to perform the post request. however, post method from HttpClient only takes a Uri whereas the one from BaseClient takes in url, headers, body, and encoding. any idea how I can set those on HttpClient's request?

    I've also looked into oauth2_client but it doesn't even support a custom http client and oauth_dio but that one only implements client credentials grant whereas my server only supports authorization code grant.

  • mmbrian
    mmbrian almost 4 years
    I've tried that as well by specifying both user and system certificates in network security config file. however, this seems to be more of a flutter issue than an android issue. I'd expect to get a PlatformException in case android blocks any request. and seeing that it can still work if I get to use a proper http client (even without providing any security config file in the android config part of the flutter app) even more points towards this not being android specific.
  • mmbrian
    mmbrian almost 4 years
    forgot to mention, this is my attempt at recreating an existing (working) android app in flutter, I have an android-only version which works great with the same OAuth2.0 server and has no problem with self-signed certificates.
  • Gary Archer
    Gary Archer almost 4 years
    Ah - had a quick look at Dart and concepts are similar to other languages - see my updated answer.
  • mmbrian
    mmbrian almost 4 years
    thanks, option 2 is pretty much what I've tried and it only works on HttpClient. no way apparently to make it work with dart io's client which is used by most of oauth libraries. I've tried 4 of them and only oauth2_client (pub.dev/packages/oauth2_client) seemed like it at least allows a HttpClient input during authorization, but then gives an error saying "NoSuchMethodError: Class '_HttpClient' has no instance method 'post' with matching arguments." which expects exactly the same arguments as dart:io's client post method.
  • mmbrian
    mmbrian almost 4 years
    I've also tried modifying HttpOverrides.global by setting it to one that creates a HttpClient with an appropriate security context and when using oauth2_client this code gives a TlsException with "CERT_ALREADY_IN_HASH_TABLE" which means certain methods used by the library during authorization that perform a post/get (supposedly the ones that use dart:http's HttpClient) already recognize the user certificates added to the system.
  • mmbrian
    mmbrian almost 4 years
    also option 1 is kinda out of the question for us at this point since this is actually part of our production code, for an app that runs on a company network and is not connected to web. the app uses a certificate which it initially fetches from an unsecured http request, and then asks user to install it on their device. I know, still no good excuse to use self-signed certificates in production but that's not my call, I'm just the app developer.
  • mmbrian
    mmbrian almost 4 years
    I have found a potential solution using oauth_dio (pub.dev/packages/oauth_dio) but need to update backend part to allow at least client credentials grant as this library does not support code grant.
  • Gary Archer
    Gary Archer almost 4 years
    Interesting to read about your pain points. I have seen it a lot with various mobile tech stacks. I've posted some notes on this above. You should not use client credentials grant in a mobile app - aim for a flow in line with security guidance. Mobile OAuth is difficult though, and stakeholders often don't understand the challenge.