I have a use case where a user, on Flutter Web, needs to link an Apple auth with their existing account, and the email may not match.
However, the only available method for Flutter Web Apple Authentication is signInWithPopUp. If the user's apple email is different from the User firebase account email, a new firebase account is created, and a user is returned, short circuiting the process of linking, this creates a new account in firebase, and I'm unable to linkWithCredential.
My method to try to link accounts is as follows:
Future<String> linkWithAppleWeb() async {
try {
final User user = _auth.currentUser!;
final provider = OAuthProvider("apple.com")
..addScope('email')
..addScope('name');
await _auth.signInWithPopup(provider).then((appleCredential) async {
final authCredential = appleCredential.credential;
await user.linkWithCredential(authCredential!).then((result) {
DatabaseService().updateUserSocialAuth(user.uid, 'apple');
return 'Success';
}).catchError((e) {
return 'Error: $e';
});
});
} catch (e) {
return 'Error: $e';
}
return 'Success';
}
As you would expect, my project starts with Streaming a User Object, and when the pop up signs in, it returns the new user, which rebuilds the entire project. Is there a way to authenticate an apple user without returning a new user? I can link a google or phone authorization method fine. It's apple that is problematic. I don't fully understand why Google doesn't break in the same way, other than Firebase put in the work to ensure the functionality of linking for GoogleSignIn().signIn() I'm not using other social auth methods, and I don't use password/email.
This method is not documented in the Flutter Fire Docs, but works perfectly:
Future<String> linkWithAppleWeb() async {
try {
final User user = _auth.currentUser!;
final provider = OAuthProvider("apple.com")
..addScope('email')
..addScope('name');
await user.linkWithPopup(provider).then((result) {
DatabaseService().updateUserSocialAuth(user.uid, 'apple');
return 'Success';
}).catchError((e) {
return 'Error: $e';
});
} catch (e) {
debugPrint('auth linkWithGoogle error: ${e.toString()}');
return 'Error: $e';
}
return 'Success';
}
Related
I'm working on an application for my organization. I was trying to implement oAuth with the google_sign_in package based on our google accounts.
The process for members to log in with accounts that belongs to the organization works fine. That's not the case with external emails.
I was expecting an error to be thrown in google prompt but I'm unable to log in with different at all. Seams that google tries to log me in instantly each time, leading to this screen each time:
I've tried using GoogleSignIn().signOut() or .deactivate() methods both of those did nothing to either remove the cache or retrigger the signing process.
Code responsible for log in:
class FirebaseAuthExternalIdentityProvider implements ExternalIdentityProvider {
final FirebaseAuth _firebaseAuth;
FirebaseAuthExternalIdentityProvider({required FirebaseAuth firebaseAuth})
: _firebaseAuth = firebaseAuth;
#override
Future<Result<GoogleSignInTokenWrapper>> signInWithGoogle() async {
final GoogleSignInAccount? googleAccount = await GoogleSignIn().signIn();
if (googleAccount == null) {
return Result.failure(Failure.signInProcessAborted());
}
final GoogleSignInAuthentication? googleAuthStatus =
await googleAccount.authentication;
final OAuthCredential _firebaseCredential = GoogleAuthProvider.credential(
accessToken: googleAuthStatus?.accessToken,
idToken: googleAuthStatus?.idToken,
);
try {
return Result.success(GoogleSignInTokenWrapper(
accessToken: await (await _firebaseAuth
.signInWithCredential(_firebaseCredential))
.user!
.getIdToken(),
));
} on FirebaseAuthException catch (cause) {
return Result.failure(Failure.firebaseSignInException(cause: cause));
} on Exception catch (cause) {
return Result.failure(Failure.unexpected(cause: cause));
}
}
}
Does someone know how to handle this problem? Changing the OAuth consent screen to External is the last thing I want to consider doing.
Thanks for any help in this matter!
I am implementing a password recovery function based on the url sent to the email. Opening the app based on that url was successful. But instead of directly opening the required page in the app that is in the background, it duplicates the app. Although it still leads me to the password recovery page, now there will be 2 same apps running side by side
Procedure
Enter your email to send the password reset link
Click submit
Open the email containing the recovery link
Duplicate the app and open a recovery password page
Things what happen
Splash screen, first page open in the app, I am trying to do as instructed from uni_links package but still no success. Currently the function getInitialLink has the effect of opening the app based on the recovery link
class SplashController extends GetxController {
final SharedPreferencesHelper _helper = Get.find<SharedPreferencesHelper>();
late StreamSubscription sub;
#override
void onReady() async {
super.onReady();
await checkToken();
}
Future<void> checkToken() async {
await Future.delayed(Duration(seconds: 3));
var token = _helper.getToken();
if (token == null) {
Get.offNamed(Routes.LOGIN);
} else {
Get.offNamed(Routes.MAIN);
}
}
#override
void onInit() {
super.onInit();
initUniLinks();
}
Future<Null> initUniLinks() async {
// Platform messages may fail, so we use a try/catch PlatformException.
try {
String? initialLink = await getInitialLink();
if (initialLink != null) {
print("okay man");
Get.toNamed(Routes.RECOVERY);
}
sub = getLinksStream().listen((link) {
}, onError: (err) {
});
} on PlatformException {
// Handle exception by warning the user their action did not succeed
// return?
}
}
}
I found the solution, actually this answer is already on Stackoverflow, and it's really simple.
In the AndroidManifest.xml file of the app. Find "android:launchMode" and change its old value to singleTask. And here is the result
android:launchMode="singleTask"
So I am running the tests for my auth implementation in flutter and I seem to be getting a rather peculiar error. This is my test:
group('When remotedatabase calls signInWithGoogle', () {
group('and signs into google successfully', () {
setUp(() {
when(mockGoogleSignIn.signIn())
.thenAnswer((realInvocation) async => mockGoogleSignInAccount);
});
test(
'should return void if signin into firebase is successful',
() async {
// arrange
when(mockFirebaseAuth.signInWithCredential(any))
.thenAnswer((realInvocation) async => mockUserCredential);
// act
await remoteDatabaseImpl.signInWithGoogle();
// assert
verify(mockGoogleSignIn.signIn());
//=> This verification passes <=//
verify(mockFirebaseAuth.signInWithCredential(any));
},
);
test(
'should throw an authexception if unable to sign into firebase with credentials',
() async {
// arrange
when(mockFirebaseAuth.signInWithCredential(any))
.thenThrow(FirebaseAuthException(code: terror));
// act
final call = remoteDatabaseImpl.signInWithGoogle;
// assert
expectLater(call, throwsA(isA<AuthException>()));
verify(mockGoogleSignIn.signIn());
//=> This is the verification that fails <=//
verify(mockFirebaseAuth.signInWithCredential(any));
},
);
});
});
When I run the test, this is my output:
✓ When remotedatabase calls signInWithGoogle and signs into google successfully should return void if
signin into firebase is successful
No matching calls (actually, no calls at all).
(If you called `verify(...).called(0);`, please instead use `verifyNever(...);`.)
package:test_api fail
_VerifyCall._checkWith
package:mockito/src/mock.dart:631
_makeVerify.<fn>
package:mockito/src/mock.dart:926
2
main.<fn>.<fn>.<fn>
test\…\auth_data_sources\auth_remote_database_impl_test.dart:123
2
✖ When remote database calls signInWithGoogle and signs into google successfully should throw an
auth exception if unable to sign into firebase with credentials
This is my implementation:
#override
Future<void> signInWithGoogle() async {
AuthCredential authCredential;
// signin with google first
await googleSignIn.signIn().then(
(GoogleSignInAccount googleAccount) async =>
await googleAccount.authentication.then(
(GoogleSignInAuthentication googleauth) async {
authCredential = GoogleAuthProvider.credential(
accessToken: googleauth.accessToken,
idToken: googleauth.idToken,
);
// finally sign with firebaseauth
try {
await firebaseAuth.signInWithCredential(authCredential);
} on FirebaseAuthException catch (e) {
throw AuthException(e.code);
}
},
),
);
}
So as you can see the same verification seems to fail for different tests testing the same code implementation and I can't seem to figure the problem out. Please help.
Also, in the case for await firebaseAuth.signInWithCredential(authCredential); does this function create an account for the credentials if it does not exist? Because I cannot seem to find a function like await firebaseAuth.createAccountWithCredential(authCredential); and I need to create an account for the google account if it's new.
You forgot to await the expectLater:
expectLater(call, throwsA(isA<AuthException>()));
Also, if you want to catch errors like this in the future look in to the dart linting options. unawaited_futures would have alerted you about this
As for the second part, yes it does create an account if the user doesnt have one yet.
This is also documented on the function itself
I'm building an app in flutter (latest build) for students on the university. every student has its own email and password (ex. s2170456#student.utwente.nl), which is integrated in google, so everyone is able to see notifications in Gmail.
This does also mean; if you want to log in with google, your studentEmail is an option to do so. I want to implement a google log-in feature where only student of the university (with their email sXXXXXXX#student.utwente.nl) are able to login.
My question is: is there a way to filter on the google login email? I thought about using normal email login and use RegEx to validate, but this means student should firstly signup. I would like to skip the whole sign-up and let the students use their already owned student email to signin.
it should look something like this (if it is even possible) VV
Widget googleLogin () {
googleLogin button()
if(googlelogin.email == sXXXXXXX#student.utwente.nl)
log user in;
} else {
return error message('invalid email')
}
I want this to be able to only register and login user with student email.
You can use google sign in integration like this.
GoogleSignInAccount _currentUser;
then in init state:-
#override
void initState() {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount account) {
setState(() {
_currentUser = account;
print("User Name ${_currentUser.displayName}");
print("User Email ${_currentUser.email}");
});
if (_currentUser != null) {
var socialData = SocialData(
_currentUser.displayName, "", _currentUser.email, LoginType.GOOGLE);
_startHomeScreen(socialData);
} else {
_showError('Error, Please try again later');
}
});
}
on successful login, this will execute:-
_startHomeScreen(SocialData data) {
Navigator.push(context, MaterialPageRoute(builder: (context) {
return Home(socialData: data);
}));
}
Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
Call _handleSignIn() on click of google sign in button.
The solution is to let anyone sign in but block the users from using the app unless they are using the university mail
void handleUser(FirebaseUser user) {
if(user.email==sXXXXXXX#student.utwente.nl) {
// handle sign in and UI changes
} else {
print('The user is not allowed');
}
}
I'm following the instructions for incorporating facebook with android projects found here https://developers.facebook.com/apps/318154048893918/fb-login/quickstart/ and there is a step to download the Facebook SDK, but after that, it doesn't tell me where to put the file. The import statement it tells me to add won't work (says target of uri doesn't exist).
I'm trying to add the facebook user to our firebase database when they log in. I'm using flutter in android studio.
There doesn't seem to be anything of use in the console log, except that print statement doesn't print anything. Any ideas?
Here's my code to log in the user.
import com.facebook.FacebookSdk;
import com.facebook.appevents.AppEventsLogger;
Future<FirebaseUser> initiateFacebookLogin() async {
final FacebookLoginResult result =
await facebookLogin.logInWithReadPermissions(['email', 'public_profile']);
FirebaseUser user =
await _auth.signInWithFacebook(accessToken: result.accessToken.token);
//Token: ${accessToken.token}
ProviderDetails userInfo = new ProviderDetails(
user.providerId, user.uid, user.displayName, user.photoUrl, user.email);
List<ProviderDetails> providerData = new List<ProviderDetails>();
providerData.add(userInfo);
print(user.displayName);
addToDatabase(user.uid, user.displayName, user.displayName, user.email);
return user;
}
In flutter you need use flutter_facebook_login plugin take a look here to see how to get the plugin and setup your flutter app to make use of this plugin. You can also check this article that is step-by-step about how setup you project and contains code example too but the API used is out of date.
Here a snippet with updated API showing how to achieve login in firebase with facebook account.
/// This mehtod makes the real auth
Future<FirebaseUser> firebaseAuthWithFacebook({#required FacebookAccessToken token}) async {
AuthCredential credential= FacebookAuthProvider.getCredential(accessToken: token.token);
FirebaseUser firebaseUser = await _authInstance.signInWithCredential(credential);
return firebaseUser;
}
In your code you're using _auth.signInWithFacebook method that is deprecated and you should replaced by signInWithCredential updating you firebase_auth plugin version.
///This object comes from facebook_login_plugin package
final facebookLogin = new FacebookLogin();
final facebookLoginResult = await facebookLogin
.logInWithReadPermissions(['email', 'public_profile']);
switch (facebookLoginResult.status) {
case FacebookLoginStatus.error:
print("Error");
break;
case FacebookLoginStatus.cancelledByUser:
print("CancelledByUser");
break;
case FacebookLoginStatus.loggedIn:
print("LoggedIn");
/// calling the auth mehtod and getting the logged user
var firebaseUser = await firebaseAuthWithFacebook(
token: facebookLoginResult.accessToken);
}
}