1
1
import 'dart:async' ;
2
+ import 'dart:convert' ;
3
+ import 'dart:io' ;
2
4
5
+ import 'package:crypto/crypto.dart' ;
6
+ import 'package:flutter/foundation.dart' ;
3
7
import 'package:flutter/material.dart' ;
4
8
import 'package:font_awesome_flutter/font_awesome_flutter.dart' ;
9
+ import 'package:google_sign_in/google_sign_in.dart' ;
10
+ import 'package:sign_in_with_apple/sign_in_with_apple.dart' ;
5
11
import 'package:supabase_auth_ui/src/utils/constants.dart' ;
6
12
import 'package:supabase_flutter/supabase_flutter.dart' ;
7
13
@@ -20,7 +26,7 @@ extension on OAuthProvider {
20
26
OAuthProvider .slack => FontAwesomeIcons .slack,
21
27
OAuthProvider .spotify => FontAwesomeIcons .spotify,
22
28
OAuthProvider .twitch => FontAwesomeIcons .twitch,
23
- OAuthProvider .twitter => FontAwesomeIcons .x ,
29
+ OAuthProvider .twitter => FontAwesomeIcons .xTwitter ,
24
30
_ => Icons .close,
25
31
};
26
32
@@ -58,8 +64,31 @@ enum SocialButtonVariant {
58
64
iconAndText,
59
65
}
60
66
67
+ class NativeGoogleAuthConfig {
68
+ /// Web Client ID that you registered with Google Cloud.
69
+ ///
70
+ /// Required to perform native Google Sign In on Android
71
+ final String ? webClientId;
72
+
73
+ /// iOS Client ID that you registered with Google Cloud.
74
+ ///
75
+ /// Required to perform native Google Sign In on iOS
76
+ final String ? iosClientId;
77
+
78
+ const NativeGoogleAuthConfig ({
79
+ this .webClientId,
80
+ this .iosClientId,
81
+ });
82
+ }
83
+
61
84
/// UI Component to create social login form
62
85
class SupaSocialsAuth extends StatefulWidget {
86
+ /// Defines native google provider to show in the form
87
+ final NativeGoogleAuthConfig ? nativeGoogleAuthConfig;
88
+
89
+ /// Whether to use native Apple sign in on iOS and macOS
90
+ final bool enableNativeAppleAuth;
91
+
63
92
/// List of social providers to show in the form
64
93
final List <OAuthProvider > socialProviders;
65
94
@@ -77,7 +106,7 @@ class SupaSocialsAuth extends StatefulWidget {
77
106
final String ? redirectUrl;
78
107
79
108
/// Method to be called when the auth action is success
80
- final void Function (Session ) onSuccess;
109
+ final void Function (Session session ) onSuccess;
81
110
82
111
/// Method to be called when the auth action threw an excepction
83
112
final void Function (Object error)? onError;
@@ -93,6 +122,8 @@ class SupaSocialsAuth extends StatefulWidget {
93
122
94
123
const SupaSocialsAuth ({
95
124
Key ? key,
125
+ this .nativeGoogleAuthConfig,
126
+ this .enableNativeAppleAuth = true ,
96
127
required this .socialProviders,
97
128
this .colored = true ,
98
129
this .redirectUrl,
@@ -111,6 +142,63 @@ class SupaSocialsAuth extends StatefulWidget {
111
142
class _SupaSocialsAuthState extends State <SupaSocialsAuth > {
112
143
late final StreamSubscription <AuthState > _gotrueSubscription;
113
144
145
+ /// Performs Google sign in on Android and iOS
146
+ Future <AuthResponse > _nativeGoogleSignIn ({
147
+ required String ? webClientId,
148
+ required String ? iosClientId,
149
+ }) async {
150
+ final GoogleSignIn googleSignIn = GoogleSignIn (
151
+ clientId: iosClientId,
152
+ serverClientId: webClientId,
153
+ );
154
+
155
+ final googleUser = await googleSignIn.signIn ();
156
+ final googleAuth = await googleUser! .authentication;
157
+ final accessToken = googleAuth.accessToken;
158
+ final idToken = googleAuth.idToken;
159
+
160
+ if (accessToken == null ) {
161
+ throw const AuthException (
162
+ 'No Access Token found from Google sign in result.' );
163
+ }
164
+ if (idToken == null ) {
165
+ throw const AuthException (
166
+ 'No ID Token found from Google sign in result.' );
167
+ }
168
+
169
+ return supabase.auth.signInWithIdToken (
170
+ provider: OAuthProvider .google,
171
+ idToken: idToken,
172
+ accessToken: accessToken,
173
+ );
174
+ }
175
+
176
+ /// Performs Apple sign in on iOS or macOS
177
+ Future <AuthResponse > _nativeAppleSignIn () async {
178
+ final rawNonce = supabase.auth.generateRawNonce ();
179
+ final hashedNonce = sha256.convert (utf8.encode (rawNonce)).toString ();
180
+
181
+ final credential = await SignInWithApple .getAppleIDCredential (
182
+ scopes: [
183
+ AppleIDAuthorizationScopes .email,
184
+ AppleIDAuthorizationScopes .fullName,
185
+ ],
186
+ nonce: hashedNonce,
187
+ );
188
+
189
+ final idToken = credential.identityToken;
190
+ if (idToken == null ) {
191
+ throw const AuthException (
192
+ 'Could not find ID Token from generated Apple sign in credential.' );
193
+ }
194
+
195
+ return supabase.auth.signInWithIdToken (
196
+ provider: OAuthProvider .apple,
197
+ idToken: idToken,
198
+ nonce: rawNonce,
199
+ );
200
+ }
201
+
114
202
@override
115
203
void initState () {
116
204
super .initState ();
@@ -135,6 +223,8 @@ class _SupaSocialsAuthState extends State<SupaSocialsAuth> {
135
223
@override
136
224
Widget build (BuildContext context) {
137
225
final providers = widget.socialProviders;
226
+ final googleAuthConfig = widget.nativeGoogleAuthConfig;
227
+ final isNativeAppleAuthEnabled = widget.enableNativeAppleAuth;
138
228
final coloredBg = widget.colored == true ;
139
229
140
230
if (providers.isEmpty) {
@@ -208,12 +298,38 @@ class _SupaSocialsAuthState extends State<SupaSocialsAuth> {
208
298
);
209
299
break ;
210
300
default :
211
- // Handle other cases or provide a default behavior.
212
301
break ;
213
302
}
214
303
215
304
onAuthButtonPressed () async {
216
305
try {
306
+ // Check if native Google login should be performed
307
+ if (socialProvider == OAuthProvider .google) {
308
+ final webClientId = googleAuthConfig? .webClientId;
309
+ final iosClientId = googleAuthConfig? .iosClientId;
310
+ final shouldPerformNativeGoogleSignIn =
311
+ (webClientId != null && ! kIsWeb && Platform .isAndroid) ||
312
+ (iosClientId != null && ! kIsWeb && Platform .isIOS);
313
+ if (shouldPerformNativeGoogleSignIn) {
314
+ await _nativeGoogleSignIn (
315
+ webClientId: webClientId,
316
+ iosClientId: iosClientId,
317
+ );
318
+ return ;
319
+ }
320
+ }
321
+
322
+ // Check if native Apple login should be performed
323
+ if (socialProvider == OAuthProvider .apple) {
324
+ final shouldPerformNativeAppleSignIn =
325
+ (isNativeAppleAuthEnabled && ! kIsWeb && Platform .isIOS) ||
326
+ (isNativeAppleAuthEnabled && ! kIsWeb && Platform .isMacOS);
327
+ if (shouldPerformNativeAppleSignIn) {
328
+ await _nativeAppleSignIn ();
329
+ return ;
330
+ }
331
+ }
332
+
217
333
await supabase.auth.signInWithOAuth (
218
334
socialProvider,
219
335
redirectTo: widget.redirectUrl,
0 commit comments