mmbrian
mmbrian

Reputation: 1662

Flutter OAuth request fails when using self-signed certificate

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.

Upvotes: 2

Views: 2387

Answers (2)

Fabian Bouché
Fabian Bouché

Reputation: 21

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;

Upvotes: 1

Gary Archer
Gary Archer

Reputation: 29316

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:

Upvotes: 1

Related Questions