11import 'dart:async' ;
2+ import 'dart:convert' ;
3+ import 'dart:io' ;
24
5+ import 'package:crypto/crypto.dart' ;
6+ import 'package:flutter/foundation.dart' ;
37import 'package:flutter/material.dart' ;
48import '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' ;
511import 'package:supabase_auth_ui/src/utils/constants.dart' ;
612import 'package:supabase_flutter/supabase_flutter.dart' ;
713
@@ -20,7 +26,7 @@ extension on OAuthProvider {
2026 OAuthProvider .slack => FontAwesomeIcons .slack,
2127 OAuthProvider .spotify => FontAwesomeIcons .spotify,
2228 OAuthProvider .twitch => FontAwesomeIcons .twitch,
23- OAuthProvider .twitter => FontAwesomeIcons .x ,
29+ OAuthProvider .twitter => FontAwesomeIcons .xTwitter ,
2430 _ => Icons .close,
2531 };
2632
@@ -58,8 +64,31 @@ enum SocialButtonVariant {
5864 iconAndText,
5965}
6066
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+
6184/// UI Component to create social login form
6285class 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+
6392 /// List of social providers to show in the form
6493 final List <OAuthProvider > socialProviders;
6594
@@ -77,7 +106,7 @@ class SupaSocialsAuth extends StatefulWidget {
77106 final String ? redirectUrl;
78107
79108 /// Method to be called when the auth action is success
80- final void Function (Session ) onSuccess;
109+ final void Function (Session session ) onSuccess;
81110
82111 /// Method to be called when the auth action threw an excepction
83112 final void Function (Object error)? onError;
@@ -93,6 +122,8 @@ class SupaSocialsAuth extends StatefulWidget {
93122
94123 const SupaSocialsAuth ({
95124 Key ? key,
125+ this .nativeGoogleAuthConfig,
126+ this .enableNativeAppleAuth = true ,
96127 required this .socialProviders,
97128 this .colored = true ,
98129 this .redirectUrl,
@@ -111,6 +142,63 @@ class SupaSocialsAuth extends StatefulWidget {
111142class _SupaSocialsAuthState extends State <SupaSocialsAuth > {
112143 late final StreamSubscription <AuthState > _gotrueSubscription;
113144
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+
114202 @override
115203 void initState () {
116204 super .initState ();
@@ -135,6 +223,8 @@ class _SupaSocialsAuthState extends State<SupaSocialsAuth> {
135223 @override
136224 Widget build (BuildContext context) {
137225 final providers = widget.socialProviders;
226+ final googleAuthConfig = widget.nativeGoogleAuthConfig;
227+ final isNativeAppleAuthEnabled = widget.enableNativeAppleAuth;
138228 final coloredBg = widget.colored == true ;
139229
140230 if (providers.isEmpty) {
@@ -208,12 +298,38 @@ class _SupaSocialsAuthState extends State<SupaSocialsAuth> {
208298 );
209299 break ;
210300 default :
211- // Handle other cases or provide a default behavior.
212301 break ;
213302 }
214303
215304 onAuthButtonPressed () async {
216305 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+
217333 await supabase.auth.signInWithOAuth (
218334 socialProvider,
219335 redirectTo: widget.redirectUrl,
0 commit comments