Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implemented deep link and local server options instead web_auth #74

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
114 changes: 105 additions & 9 deletions example/pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,38 @@ packages:
url: "https://pub.dev"
source: hosted
version: "6.2.0"
app_links:
dependency: transitive
description:
name: app_links
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
url: "https://pub.dev"
source: hosted
version: "6.3.2"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.dev"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.dev"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.dev"
source: hosted
version: "1.0.4"
args:
dependency: transitive
description:
Expand Down Expand Up @@ -203,14 +235,6 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
flutter_web_auth:
dependency: transitive
description:
name: flutter_web_auth
sha256: a69fa8f43b9e4d86ac72176bf747b735e7b977dd7cf215076d95b87cb05affdd
url: "https://pub.dev"
source: hosted
version: "0.5.0"
flutter_web_plugins:
dependency: transitive
description: flutter
Expand All @@ -232,6 +256,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "2.1.2"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.dev"
source: hosted
version: "2.1.0"
http:
dependency: "direct main"
description:
Expand Down Expand Up @@ -620,6 +652,70 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.3.2"
url_launcher:
dependency: transitive
description:
name: url_launcher
sha256: "9d06212b1362abc2f0f0d78e6f09f726608c74e3b9462e8368bb03314aa8d603"
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193"
url: "https://pub.dev"
source: hosted
version: "6.3.14"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: e43b677296fadce447e987a2f519dcf5f6d1e527dc35d01ffab4fff5b8a7063e
url: "https://pub.dev"
source: hosted
version: "6.3.1"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: "4e9ba368772369e3e08f231d2301b4ef72b9ff87c31192ef471b380ef29a4935"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "769549c999acdb42b8bcfa7c43d72bf79a382ca7441ab18a808e101149daf672"
url: "https://pub.dev"
source: hosted
version: "3.2.1"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b
url: "https://pub.dev"
source: hosted
version: "2.2.3"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: "44cf3aabcedde30f2dba119a9dea3b0f2672fbe6fa96e85536251d678216b3c4"
url: "https://pub.dev"
source: hosted
version: "3.1.3"
vector_math:
dependency: transitive
description:
Expand Down Expand Up @@ -702,4 +798,4 @@ packages:
version: "3.1.2"
sdks:
dart: ">=3.5.0 <4.0.0"
flutter: ">=3.18.0-18.0.pre.54"
flutter: ">=3.24.0"
106 changes: 93 additions & 13 deletions lib/logto_client.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import 'package:flutter_web_auth/flutter_web_auth.dart';
import 'dart:async';
import 'dart:io';

import 'package:app_links/app_links.dart';
import 'package:url_launcher/url_launcher.dart';
import 'package:http/http.dart' as http;
import 'package:jose/jose.dart';

import '/src/utilities/stream_awaiter.dart';
import '/src/modules/callback_strategy.dart';
import '/src/exceptions/logto_auth_exceptions.dart';
import '/src/interfaces/logto_interfaces.dart';
import '/src/modules/id_token.dart';
Expand All @@ -15,6 +21,7 @@ import '/src/utilities/constants.dart';
export '/src/exceptions/logto_auth_exceptions.dart';
export '/src/interfaces/logto_interfaces.dart';
export '/src/utilities/constants.dart';
export '/src/modules/callback_strategy.dart';

/**
* LogtoClient
Expand All @@ -35,6 +42,8 @@ export '/src/utilities/constants.dart';
*
* final logtoClient = LogtoClient(config);
*/
final appLinks = AppLinks();

class LogtoClient {
final LogtoConfig config;

Expand All @@ -53,13 +62,26 @@ class LogtoClient {

OidcProviderConfig? _oidcConfig;

LogtoClient({
required this.config,
LogtoStorageStrategy? storageProvider,
http.Client? httpClient,
}) {
late final CallbackStrategy _callbackStrategy;

LogtoClient(
{required this.config,
LogtoStorageStrategy? storageProvider,
http.Client? httpClient,
CallbackStrategy? callbackStrategy}) {
_httpClient = httpClient;
_tokenStorage = TokenStorage(storageProvider);
_callbackStrategy = callbackStrategy ?? SchemeStrategy();
}

final _authController = StreamController<bool>.broadcast();

Stream<bool> get isAuthenticatedStream => _authController.stream;

void init() async {
bool value = await isAuthenticated;

_authController.sink.add(value);
}

// Use idToken to check if the user is authenticated.
Expand Down Expand Up @@ -159,6 +181,8 @@ class LogtoClient {
final idToken = IdToken.unverified(response.idToken!);
await _verifyIdToken(idToken, oidcConfig);
await _tokenStorage.setIdToken(idToken);

_authController.sink.add(true);
}

return await _tokenStorage.getAccessToken(
Expand Down Expand Up @@ -227,13 +251,7 @@ class LogtoClient {
extraParams: extraParams,
);

final redirectUriScheme = Uri.parse(redirectUri).scheme;

final String callbackUri = await FlutterWebAuth.authenticate(
url: signInUri.toString(),
callbackUrlScheme: redirectUriScheme,
preferEphemeral: true,
);
final String callbackUri = await _getCallbackUrl(signInUri);

await _handleSignInCallback(callbackUri, redirectUri, httpClient);
} finally {
Expand Down Expand Up @@ -272,6 +290,8 @@ class LogtoClient {
refreshToken: tokenResponse.refreshToken,
expiresIn: tokenResponse.expiresIn,
scopes: tokenResponse.scope.split(' '));

_authController.sink.add(true);
}

// Sign out the user.
Expand Down Expand Up @@ -306,6 +326,7 @@ class LogtoClient {
}

await _tokenStorage.clear();
_authController.sink.add(false);
} finally {
if (_httpClient == null) {
httpClient.close();
Expand Down Expand Up @@ -338,4 +359,63 @@ class LogtoClient {
if (_httpClient == null) httpClient.close();
}
}

Future<String> _getCallbackUrl(Uri url) async {
final LaunchMode launchMode =
_callbackStrategy.launchMode == BrowserLaunchMode.platformDefault
? LaunchMode.platformDefault
: LaunchMode.externalApplication;

if (!await launchUrl(url, mode: launchMode)) {
throw Exception('Could not launch ${url.toString()}');
}

var result = await _athuneticateUserFlow();

return result.toString();
}

Future<Uri> _athuneticateUserFlow() async {
late Uri result;

if (_callbackStrategy.strategy == CallbackStrategyType.scheme) {
await awaitUriLinkStream(
appLinks.uriLinkStream,
(uri) async {
if (uri != null) {
result = uri;
} else {
throw Exception("Failed to authorize");
}
},
);

return result;
}

final server = await HttpServer.bind(InternetAddress.anyIPv4,
(_callbackStrategy as LocalServerStrategy).port);

await for (HttpRequest request in server) {
// Handle the request
request.response
..statusCode = HttpStatus.ok
..headers.contentType = ContentType.html
..writeln("""
<html>
<script>window.close();</script>
</html>
""");
await request.response.close();
result = request.requestedUri;
await server.close();
break; // Exit the loop
}

return result;
}

void dispose() {
_authController.close();
}
}
49 changes: 49 additions & 0 deletions lib/src/modules/callback_strategy.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
enum CallbackStrategyType {
scheme,
localServer
}

enum BrowserLaunchMode {
platformDefault,
external
}

abstract class CallbackStrategy {
CallbackStrategyType get strategy;
BrowserLaunchMode get launchMode;
}

class SchemeStrategy implements CallbackStrategy {
late BrowserLaunchMode _launchMode;
final CallbackStrategyType _strategy = CallbackStrategyType.scheme;

SchemeStrategy({BrowserLaunchMode? launchMode}){
_launchMode = launchMode ?? BrowserLaunchMode.platformDefault;
}

@override
CallbackStrategyType get strategy => _strategy;

@override
BrowserLaunchMode get launchMode => _launchMode;

}

class LocalServerStrategy implements CallbackStrategy {
late BrowserLaunchMode _launchMode;
final CallbackStrategyType _strategy = CallbackStrategyType.localServer;
final int _port;

LocalServerStrategy(this._port,{BrowserLaunchMode? launchMode}){
_launchMode = launchMode ?? BrowserLaunchMode.platformDefault;
}

@override
CallbackStrategyType get strategy => _strategy;

int get port => _port;

@override
BrowserLaunchMode get launchMode => throw _launchMode;

}
32 changes: 32 additions & 0 deletions lib/src/utilities/stream_awaiter.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import 'dart:async';

Future<void> awaitUriLinkStream(Stream<Uri?> uriLinkStream, Future<void> Function(Uri? uri) onUri) async {
final completer = Completer<void>();
late StreamSubscription<Uri?> subscription;

subscription = uriLinkStream.listen(
(uri) async {
try {
await onUri(uri);
completer.complete();
} catch (e) {
completer.completeError(e);
} finally {
await subscription.cancel(); // Ensure subscription is canceled
}
},
onError: (error) {
if (!completer.isCompleted) {
completer.completeError(error);
}
},
onDone: () {
if (!completer.isCompleted) {
completer.complete();
}
},
cancelOnError: true,
);

return completer.future;
}
Loading
Loading