oauth2_custom_uri_scheme 0.3.12

  • Readme
  • Changelog
  • Example
  • Installing
  • 72

IMPORTANT NOTE #

If you want to use the working stable version, use the version on github until the next release.

  oauth2_custom_uri_scheme:
    git:
      url: https://github.com/espresso3389/oauth2_custom_uri_scheme

There's a release blocker and I will release a new release if the blocker is gone.

oauth2_custom_uri_scheme #

An implementation of OAuth 2.0 authorization code grant with redirection to application specific custom URI scheme.

To make the implementation safer (but not perfect), it implements the following features:

So the implementation fully works on Android with API level >= 23 and iOS >= 11.0.

Installation #

dependencies:
 oauth2_custom_uri_scheme: ^0.3.12

Getting Started #

import 'package:oauth2_custom_uri_scheme/oauth2_custom_uri_scheme.dart';
import 'package:oauth2_custom_uri_scheme/oauth2_token_holder.dart';

...

//
// OAuth2Config can be app global to keep the OAuth configuration
//
final oauth2Config = OAuth2Config(
  uniqueId: 'example.com#1', // NOTE: ID to identify the credential for box session
  authorizationEndpoint: Uri.parse('https://example.com/authorize'),
  tokenEndpoint: Uri.parse('https://example.com/token'),
  // revocationEndpoint is optional
  revocationEndpoint: Uri.parse('https://example.com/revoke'),
  // NOTE: For Android, we also have corresponding intent-filter entry on example/android/app/src/main/AndroidManifest.xml
  redirectUri: Uri.parse('com.example.redirect43763246328://callback'),
  clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  clientSecret: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
  useBasicAuth: false);

...

Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: Text('Simple OAuth Sample'),
    ),
    body: Center(
      // OAuth2TokenHolder is the easiest way to create [authorize] button.
      child: OAuth2TokenHolder(
        config: oauth2Config,
        builder: (context, accessToken, state, authorize, deauthorize, child) => ListTile(title: RaisedButton(
          onPressed:() => accessToken == null ? authorize() : deauthorize(),
          child: state == OAuth2TokenAvailability.Authorizing
          ? const CircularProgressIndicator()
          : Text(accessToken == null ? 'Authorize' : 'Deauthorize'))
        )
      )
    )
  );
}

// After [Authorize] on the UI, we can get the access token from cache.
AccessToken token = await oauth2Config.getAccessTokenFromCache();

// Or, of course, you can authorize the app
// If reset=false, it may use the cache and the method returns immediately;
// otherwise reset=true, it always tries to authorize the app.
AccessToken token = await oauth2Config.authorize(reset: true);

// OK, we can execute GET query
final foobarResult = await token.getJsonFromUri('https://example.com/api/2.0/foobar');

Lower level API #

To use the plugin with your OAuth service provider, call AccessToken.authorize with your OAuth service's endpoints and client configuration:

final AccessToken token = await AccessToken.authorize(
    authorizationEndpoint: Uri.parse('https://example.com/authorize'),
    tokenEndpoint: Uri.parse('https://example.com/token'),
    // NOTE: For Android, we also have corresponding intent-filter entry on example/android/app/src/main/AndroidManifest.xml
    redirectUri: Uri.parse('com.example.redirect43763246328://callback'),
    clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
    clientSecret: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
    useBasicAuth: false, // certain services such as Box does not support Basic Auth
    idForCache: 'example.com#1' // used when caching access token
);
if (token == null) {
    // error handling
}

// OK, we can execute GET query
final foobarResult = await token.getJsonFromUri('https://example.com/api/2.0/foobar');

// POST query
final request = await token.createRequest('POST', 'https://example.com/api/2.0/zzzzz');
request.body = '....';
final response = await request.send();

Additional settings on Android #

For Android, we should update several configurations:

We should add AndroidManifest.xml to include additional <intent-filter> under <activity>.

<intent-filter>
    <action android:name="android.intent.action.VIEW" />
    <category android:name="android.intent.category.DEFAULT" />
    <category android:name="android.intent.category.BROWSABLE" />
    <!-- Should match to the one on example/lib/main.dart -->
    <data android:scheme="com.example.redirect43763246328" android:host="callback" />
</intent-filter>

In android/app/build.gradle set minSdkVersion to >= 18.

android {
    ...
    defaultConfig {
        ...
        minSdkVersion 18
        ...
    }
}

API reference #

API reference on pub.dev.

Security considerations #

On every platform, we can define a custom URI scheme to launch our app on URI redirects.

Although the URI may be something like myapp://localhost, the URI scheme here, myapp is not suitable for accepting authorization code redirect. If any other apps may use the same URI scheme, the authorization code may be intercepted by them without launching our app.

Basically, it's almost impossible to hide our custom URI scheme from others, apparently, we should choose one carefully not to conflict with other apps.

At least, we should use app scheme like com.example.mwl5oodcb9, which contains some random characters (but they should be lowercase anyway).

To reduce the risk of others intercepting authorization code, the plugin implements RFC 7636: Proof Key for Code Exchange (PKCE) by OAuth Public Clients. But not every OAuth service implements it.

0.3.12 #

  • Just a documentation update.

0.3.11 #

  • ASWebAuthenticationSession.start sliently fails on iOS13 due to breaking changes.

0.3.10 #

TEMPORARY RELEASE

Because 0.3.7 - 0.3.9 breaks consistency on certain environment, 0.3.10 is just a copy of 0.3.6 and has known issues #5.

Until flutter_inappwebview's next release, I could not release a new version......... https://github.com/pichillilorenzo/flutter_inappwebview/issues/220#issuecomment-580783367

0.3.6 #

  • token endpoint also needs redirect_uri.

0.3.5 #

  • code_challenge is not correctly calculated.

0.3.4 #

  • refresh throws exception if it could not refresh the token.
  • Add AccessToken.printHandler static variable to customize debug log verbosity.

0.3.2 #

  • OAuth2Config.authorize: reset should be false by default.

0.3.1 #

  • Minor updates.

0.3.0 #

  • BREAKING CHANGE: OAuth2Config is moved to oauth2_custom_uri_scheme.dart.
  • Add several helper methods for POST/PUT requests.

0.2.0 #

  • Introducing easy to use widgets; OAuth2Config and OAuth2TokenHolder.

0.1.1 #

  • Fix build issue on iOS caused by podspec.

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:oauth2_custom_uri_scheme/oauth2_custom_uri_scheme.dart';
import 'package:oauth2_custom_uri_scheme/oauth2_token_holder.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Simple OAuth Sample',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key}) : super(key: key);

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

final oauth2Config = OAuth2Config(
  uniqueId: 'example.com#1', // NOTE: ID to identify the credential save
  authorizationEndpoint: Uri.parse('https://login.microsoftonline.com/common/oauth2/v2.0/authorize'),
tokenEndpoint: Uri.parse('https://login.microsoftonline.com/common/oauth2/v2.0/token'),
  // revocationEndpoint is optional
  revocationEndpoint: Uri.parse('https://example.com/revoke'),
  // NOTE: For Android, we also have corresponding intent-filter entry on example/android/app/src/main/AndroidManifest.xml
  redirectUri: Uri.parse('com.example.redirect43763246328://callback'),
  clientId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
  clientSecret: 'yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy',
  scope: 'user.read',
  useBasicAuth: false);

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Simple Azure AD OAuth2.0 Example'),
      ),
      body: Center(
        child: OAuth2TokenHolder(
          config: oauth2Config,
          builder: (context, accessToken, state, authorize, deauthorize, child) => ListTile(title: RaisedButton(
            onPressed:() => accessToken == null ? authorize() : deauthroizeConfirm(deauthorize),
            child: state == OAuth2TokenAvailability.Authorizing
            ? const CircularProgressIndicator()
            : Text(accessToken == null ? 'Authorize' : 'Deauthorize'))
          )
        )
      )
    );
  }

  /// NOTE: it's your app's responsibility to interact with the user; the deauthorize function does not interact with him/her.
  Future<bool> deauthroizeConfirm(void deauthorize()) async {
    final ret = await showDialog<bool>(
      context: context,
      builder: (context) => AlertDialog(
        title: Text('Deauthorize App'),
        content: Text('Do you really want to deauthorize the app?'),
        actions: <Widget>[
          FlatButton(child: Text("Cancel"), onPressed: () => Navigator.of(context).pop(false)),
          FlatButton(child: Text("Deauthorize"), onPressed: () => Navigator.of(context).pop(true))
        ]
      )
    );
    if (ret) {
      deauthorize();
    }
    return ret;
  }
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  oauth2_custom_uri_scheme: ^0.3.12

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:oauth2_custom_uri_scheme/oauth2_custom_uri_scheme.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
48
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
90
Overall:
Weighted score of the above. [more]
72
Learn more about scoring.

We analyzed this package on Mar 31, 2020, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.7.1
  • pana: 0.13.6
  • Flutter: 1.12.13+hotfix.8

Health suggestions

Format lib/oauth2_custom_uri_scheme.dart.

Run flutter format to format lib/oauth2_custom_uri_scheme.dart.

Format lib/oauth2_token_holder.dart.

Run flutter format to format lib/oauth2_token_holder.dart.

Maintenance issues and suggestions

Support latest dependencies. (-10 points)

The version constraint in pubspec.yaml does not support the latest published versions for 1 dependency (rxdart).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
crypto ^2.1.2 2.1.4
flutter 0.0.0
flutter_custom_tabs ^0.6.0 0.6.0
flutter_secure_storage ^3.2.1+1 3.3.1+1
http ^0.12.0+2 0.12.0+4
rxdart ^0.22.2 0.22.6 0.23.1
Transitive dependencies
async 2.4.1
charcode 1.1.3
collection 1.14.11 1.14.12
convert 2.1.1
flutter_web_plugins 0.0.0
http_parser 3.1.4
meta 1.1.8
path 1.6.4
pedantic 1.9.0
plugin_platform_interface 1.0.2
sky_engine 0.0.99
source_span 1.7.0
string_scanner 1.0.5
term_glyph 1.1.0
typed_data 1.1.6
url_launcher 5.4.2
url_launcher_macos 0.0.1+4
url_launcher_platform_interface 1.0.6
url_launcher_web 0.1.1+1
vector_math 2.0.8
Dev dependencies
flutter_test