Skip to content

Commit 5b2cce4

Browse files
feat: update facebook login for limited/classic login depending on permission
1 parent d6470ca commit 5b2cce4

2 files changed

Lines changed: 102 additions & 7 deletions

File tree

packages/firebase_ui_oauth_facebook/lib/src/provider.dart

Lines changed: 100 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,15 @@
22
// for details. All rights reserved. Use of this source code is governed by a
33
// BSD-style license that can be found in the LICENSE file.
44

5+
import 'dart:convert';
6+
import 'dart:math';
7+
import 'package:crypto/crypto.dart';
58
import 'package:firebase_auth/firebase_auth.dart' as fba;
69
import 'package:flutter/foundation.dart';
710
import 'package:firebase_ui_oauth/firebase_ui_oauth.dart';
811
import 'package:flutter_facebook_auth/flutter_facebook_auth.dart';
912
import 'package:firebase_ui_oauth_facebook/firebase_ui_oauth_facebook.dart';
13+
import 'package:app_tracking_transparency/app_tracking_transparency.dart';
1014

1115
class FacebookProvider extends OAuthProvider {
1216
@override
@@ -15,6 +19,7 @@ class FacebookProvider extends OAuthProvider {
1519
FacebookAuth provider = FacebookAuth.instance;
1620
final String clientId;
1721
final String? redirectUri;
22+
String? _rawNonce;
1823

1924
@override
2025
final style = const FacebookProviderButtonStyle();
@@ -30,11 +35,72 @@ class FacebookProvider extends OAuthProvider {
3035
this.redirectUri,
3136
});
3237

38+
/// Generates a cryptographically secure random nonce for limited login
39+
String _generateNonce([int length = 32]) {
40+
const charset =
41+
'0123456789ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvwxyz-._';
42+
final random = Random.secure();
43+
return List.generate(length, (_) => charset[random.nextInt(charset.length)])
44+
.join();
45+
}
46+
47+
/// Returns the SHA256 hash of the given string
48+
String _sha256ofString(String input) {
49+
final bytes = utf8.encode(input);
50+
final digest = sha256.convert(bytes);
51+
return digest.toString();
52+
}
53+
54+
/// Checks if tracking permission has been granted on iOS
55+
Future<bool> _hasTrackingPermission() async {
56+
// Only check on iOS
57+
if (defaultTargetPlatform != TargetPlatform.iOS) {
58+
return true; // Classic login available on Android
59+
}
60+
61+
try {
62+
final status = await AppTrackingTransparency.trackingAuthorizationStatus;
63+
return status == TrackingStatus.authorized;
64+
} catch (e) {
65+
// If there's an error checking permission, default to limited login
66+
return false;
67+
}
68+
}
69+
3370
void _handleResult(LoginResult result, AuthAction action) {
3471
switch (result.status) {
3572
case LoginStatus.success:
36-
final token = result.accessToken!.token;
37-
final credential = fba.FacebookAuthProvider.credential(token);
73+
final accessToken = result.accessToken;
74+
if (accessToken == null) {
75+
authListener.onError(Exception('Access token is null'));
76+
return;
77+
}
78+
79+
fba.OAuthCredential credential;
80+
81+
// Check the token type to determine if it's classic or limited login
82+
if (accessToken.type == AccessTokenType.classic) {
83+
// Classic login - use access token
84+
credential =
85+
fba.FacebookAuthProvider.credential(accessToken.tokenString);
86+
} else if (accessToken.type == AccessTokenType.limited) {
87+
// Limited login - use ID token with nonce
88+
if (_rawNonce == null) {
89+
authListener.onError(
90+
Exception('Nonce not generated for limited login'),
91+
);
92+
return;
93+
}
94+
credential = fba.OAuthProvider(providerId).credential(
95+
idToken: accessToken.tokenString,
96+
rawNonce: _rawNonce,
97+
);
98+
} else {
99+
authListener.onError(
100+
Exception('Unknown access token type: ${accessToken.type}'),
101+
);
102+
return;
103+
}
38104

39105
onCredentialReceived(credential, action);
40106
break;
@@ -69,11 +135,38 @@ class FacebookProvider extends OAuthProvider {
69135
}
70136

71137
@override
72-
void mobileSignIn(AuthAction action) {
73-
final result = provider.login();
74-
result
75-
.then((result) => _handleResult(result, action))
76-
.catchError(authListener.onError);
138+
void mobileSignIn(AuthAction action) async {
139+
try {
140+
// Check if tracking permission is granted
141+
final hasPermission = await _hasTrackingPermission();
142+
143+
// Determine login tracking mode
144+
final loginTracking =
145+
hasPermission ? LoginTracking.enabled : LoginTracking.limited;
146+
147+
// Generate nonce for limited login
148+
if (loginTracking == LoginTracking.limited) {
149+
_rawNonce = _generateNonce();
150+
final hashedNonce = _sha256ofString(_rawNonce!);
151+
152+
// Perform login with nonce
153+
final result = await provider.login(
154+
permissions: ['email', 'public_profile'],
155+
loginTracking: loginTracking,
156+
nonce: hashedNonce,
157+
);
158+
_handleResult(result, action);
159+
} else {
160+
// Perform classic login without nonce
161+
final result = await provider.login(
162+
permissions: ['email', 'public_profile'],
163+
loginTracking: loginTracking,
164+
);
165+
_handleResult(result, action);
166+
}
167+
} catch (error) {
168+
authListener.onError(error);
169+
}
77170
}
78171

79172
@override

packages/firebase_ui_oauth_facebook/pubspec.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ dependencies:
1313
flutter:
1414
sdk: flutter
1515
flutter_facebook_auth: ^7.1.2
16+
app_tracking_transparency: ^2.0.6+1
17+
crypto: ^3.0.3
1618

1719
dev_dependencies:
1820
flutter_test:

0 commit comments

Comments
 (0)