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' ;
58import 'package:firebase_auth/firebase_auth.dart' as fba;
69import 'package:flutter/foundation.dart' ;
710import 'package:firebase_ui_oauth/firebase_ui_oauth.dart' ;
811import 'package:flutter_facebook_auth/flutter_facebook_auth.dart' ;
912import 'package:firebase_ui_oauth_facebook/firebase_ui_oauth_facebook.dart' ;
13+ import 'package:app_tracking_transparency/app_tracking_transparency.dart' ;
1014
1115class 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
0 commit comments