Firebase reauthenticate during startup with Flutter - firebase

The idea is here to keep the user logged in even after the user has closed the app and relaunch it at much later time.
I have the following code which logs in user and then try to store the tokens
Future<User> loginSocialFirebaseGoogle() async {
GoogleSignInAccount signInResult = await _googleSignIn.signIn();
GoogleSignInAuthentication auth = await signInResult.authentication;
final AuthCredential authCredential = GoogleAuthProvider.getCredential(
idToken: auth.idToken, accessToken: auth.accessToken);
final AuthResult authResult =
await _auth.signInWithCredential(authCredential);
final FirebaseUser firebaseUser = authResult.user;
assert(!firebaseUser.isAnonymous);
assert(await firebaseUser.getIdToken() != null);
final FirebaseUser currentUser = await _auth.currentUser();
assert(firebaseUser.uid == currentUser.uid);
final SharedPreferences pref = await SharedPreferences.getInstance();
pref.setString("idToken", auth.idToken);
pref.setString("accessToken", auth.accessToken);
pref.setString("provider", describeEnum(Provider.Google));
return await authResultToUser(authResult, Provider.Google);
}
Another portion of the code is called during app startup
Future<User> getCurrentUser() async {
final SharedPreferences pref = await SharedPreferences.getInstance();
if (pref != null) {
AuthCredential authCredential;
String provider = pref.getString("provider");
Provider providerEnum;
if (provider != null) {
if (provider == describeEnum(Provider.Google)) {
authCredential = GoogleAuthProvider.getCredential(
idToken: pref.getString("idToken"),
accessToken: pref.getString("accessToken"));
providerEnum = Provider.Google;
} else if (provider == describeEnum(Provider.Facebook)) {
authCredential = FacebookAuthProvider.getCredential(
accessToken: pref.getString("accessToken"));
providerEnum = Provider.Facebook;
}
final AuthResult authResult =
await _auth.signInWithCredential(authCredential);
return await authResultToUser(authResult, providerEnum);
}
}
return null;
}
However I am getting error during _auth.signInWithCredential(authCredential), the error is
`ERROR_INVALID_CREDENTIAL` - If the credential data is malformed or has expired.
Looks like what I stored in the SharedPreferences are wrong. Any idea how to fix this ?

The idea is here to keep the user logged in even after the user has closed the app and relaunch it at much later time.
Firebase already keeps the user signed in, and automatically tries to restore their session without you needing to write any code for it.
But since this may require a call to the server, you may be reading _auth.currentUser before that process has completed. To detect the authentication state properly, use a listener to authStateChanges as shown in the documentation:
FirebaseAuth.instance
.authStateChanges()
.listen((User user) {
if (user == null) {
print('User is currently signed out!');
} else {
print('User is signed in!');
}
});

Related

Check if user is logged in or not before authentication using google sign in - flutter

In my flutter app, am trying to check if a user is logged in or not before authenticating the user in firebase, so if he is not then do not authenticate
Future<String> loginUserWithGoogle() async {
String returnValue = "error";
GoogleSignIn _googleSignIn = GoogleSignIn(
scopes: [
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
UserData _user = UserData();
try {
GoogleSignInAccount _googleUser = await _googleSignIn.signIn();
GoogleSignInAuthentication _googleAuth = await _googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(idToken: _googleAuth.idToken, accessToken: _googleAuth.accessToken);
UserCredential _authResult = await _auth.signInWithCredential(credential);
if (_authResult.additionalUserInfo.isNewUser) {
String userGoogleName = _authResult.user.displayName;
List userSplitName = userGoogleName.split(" ");
String userGoogleFirstName = userSplitName.first;
String userGoogleLastName = userSplitName.last;
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('googleUid', _authResult.user.uid);
await prefs.setString('googleEmail', _authResult.user.email);
await prefs.setString('googleFirstName', userGoogleFirstName);
await prefs.setString('googleLastName', userGoogleLastName);
await prefs.setString('googleUserType', "user");
returnValue = "new";
} else {
_currentUser = await RealDatabase().getUserData(_authResult.user.uid);
if (_currentUser != null) {
returnValue = "success";
}
}
} on PlatformException catch (e) {
returnValue = e.message;
} catch (e) {
print(e);
}
return returnValue;
}
}
Here what I want to check is that if it is a new user then save his google data in sharedpreference and take him to another page where he can complete some other registration and then sign him in. but what this code does is that if it is a new user it will authenticate, save the info in sharedpeference and then take him to the page and if maybe that user decided to go back to the previous page (since i use Navigator.push(context)) and still click the google sign in button again then it will take him to the home screen without him completing the other registration I want him to do because it already authenticated him first. So please is there a way to do this without first authenticating the user.
You can use stream provider to control if user logged in or not.
Here is an example how to use stream provider in your project;
https://flutterbyexample.com/lesson/stream-provider.

Flutter Firebase - failing to properly delete a Google authenticated user

I'm trying but failing to re-trigger the authentication steps that the user gets taken through when they authenticate themselves using Google sign-in, following deletion of the user. The deleted user simply gets signed in immediately (instead of being taken through the authentication steps), when using Google sign-in the second time. I want to be able to re-trigger the authentication steps for my own testing purposes.
Specifically, I've got a user who I've authenticated and signed in as per the FlutterFire documentation, i.e.
Future<UserCredential> signInWithGoogle() async {
// Trigger the authentication flow
final GoogleSignInAccount googleUser = await GoogleSignIn().signIn();
// Obtain the auth details from the request
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
// Create a new credential
final GoogleAuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
// Once signed in, return the UserCredential
return await FirebaseAuth.instance.signInWithCredential(credential);
}
I then proceed to delete the user; again, as per the FlutterFire documentation, i.e.
try {
await FirebaseAuth.instance.currentUser.delete();
} catch on FirebaseAuthException (e) {
if (e.code == 'requires-recent-login') {
print('The user must reauthenticate before this operation can be executed.');
}
}
That works, insomuch as the user is no longer listed amongst the authenticated users in the Firebase console. However, if I now proceed to call signInWithGoogle() again, then instead of getting taken through the authentication steps again (i.e. being prompted to enter an email, password, etc.), the user simply gets signed in straight away. It's as if the user hasn't been properly deleted. How would I go about re-triggering the authentication steps?
You must also call GoogleSignIn().signOut() after the Firebase sign out or delete.
In my case, I had to reauthenticate firebase user inside the delete functions try-catch as currentUser() always return null AND GoogleSignIn().signOut() didnt work. Maybe a bug.
import 'package:google_sign_in/google_sign_in.dart';
import 'package:firebase_auth/firebase_auth.dart';
final GoogleSignIn _googleSignIn = GoogleSignIn();
final FirebaseAuth _auth = FirebaseAuth.instance;
//will need to sign in to firebase auth again as currentUser always returns null
//this try-catch block should be inside the function that deletes user
try {
//FirebaseUser user = await _auth.currentUser(); //returns null so useless
//signin to google account again
GoogleSignInAccount googleSignInAccount = await _googleSignIn.signIn();
GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
//get google credentials
AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: googleSignInAuthentication.idToken,
accessToken: googleSignInAuthentication.accessToken);
//use credentials to sign in to Firebase
AuthResult authResult = await _auth.signInWithCredential(credential);
//get firebase user
FirebaseUser user = authResult.user;
print(user.email);
//delete user
await user.delete();
//signout from google sign in
await _googleSignIn.signOut();
} catch (e) {
print('Failed to delete user ' + e.toString());
}

Using user's Google account profile picture in Flutter app

Good day, I am currently using firebase/flutter to create an app where users can log in to proceed to the next page. My log in and firebase are all in working order, but an issue I'm having is being able to access the user's profile picture on the initial log in. The gif attached shows it better than I can explain, but basically when I log in the first time, the user image is not there, but if I exit and go back in (user still signed in) the picture is then loaded:
Issue: User's profile picture does not load on first log in and only appears if exiting and re-entering while still logged in.
Assumption: The user's data does not have enough time to load in by the time my data has finished loading. OR: I am not calling the data correctly on initial log in.
What I'm aiming for: Have the user's details (photo/name/email) loaded on clicking of the login button and before the next page is fully loaded.
Code:
Signin button:
Widget _signInButton() {
return OutlineButton(
splashColor: Colors.grey,
onPressed: () async {
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
print('connected');
bool result = await signInWithGoogle(); //assumed issue
if (result) {
Navigator.pushNamed(context, '/specials-page');
fireBaseAnalyticsDataObject.onLogin(result);
}
else
print("error logging in");
}
} on SocketException catch (_) {
noInternetAlertDialog(context);
print('not connected');
}
},
sign-in.dart:
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = new GoogleSignIn();
final MyTabsState tabPageObject = new MyTabsState();
Future<bool> signInWithGoogle() async {
try{
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final AuthResult authResult = await _auth.signInWithCredential(credential);
final FirebaseUser user = authResult.user;
MyTabs(
userDisplayName: user.displayName,
userPhotoUrl: user.photoUrl,
userEmail: user.email,
);
globalData.user = user; //this accesses .uid / .displayName / .email / .photoUrl
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await _auth.currentUser();
assert(user.uid == currentUser.uid);
return true;
} catch (error) {
return false;
}
}
Thank you for any and all help.
If a there's a variable that on the page that has just been updated, calling it inside setState() might solve the issue. You need to check if Image.network() is being fed with the expected user photo URL after login because the image works as expected on app resume. The code you provided doesn't demonstrate how the photo URL is being accessed and displayed on the landing page.

Link Multiple Auth Providers to an Account

I have implemented Facebook and Google sign in.
But FireBase document says this will cause an error if the same user first signs up with Facebook and later try sign in with Google (with the same email).
So I follow doc and try to configure account linking.
But I do not know how to do.
Should I try link account every time user is logged in? Problem is I not know if the user already has signed in with another auth provider.
For example, the original code has:
Google:
void _signInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
final FirebaseUser user = await _auth.signInWithCredential(credential);
}
Facebook:
void _signInWithFacebook() async {
final AuthCredential credential = FacebookAuthProvider.getCredential(
accessToken: _tokenController.text,
);
final FirebaseUser user = await _auth.signInWithCredential(credential);
}
Is correct to call every time in _signInWithFacebook() and _signInWithGoogle() :
user = await auth.linkWithCredential(credential);
For example:
void _signInWithFacebook() async {
final AuthCredential credential = FacebookAuthProvider.getCredential(
accessToken: _tokenController.text,
);
final FirebaseUser user = await _auth.signInWithCredential(credential);
user = await auth.linkWithCredential(credential); //new
}
How I can implement correctly?
Thanks!
When the user enters their email address to sign in, you'll want to use fetchSignInMethodsForEmail() to find out if that email address is already known.
If a user has already signed up with another provider, that's a good moment to ask them if they want to merge those accounts, and then call the account linking API.

Firebase Auth not working in Flutter

Somehow Firebase Authentication doesn't work in my Implementation. I implemented Google SignIn with Firebase Authentication following the Firebase Codelab, but changed the ensureLoggedIn function to:
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
print('SIGNIN');
if (user == null) user = await googleSignIn.signInSilently();
if (user == null) {
user = await googleSignIn.signIn();
print('LOGIN');
}
if (auth.currentUser == null) {
GoogleSignInAuthentication credentials =
await googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
print('CURRENTUSER');
}
}
where I used the console outputs to verify which parts of the function are executed. The function call happens every time the app is started, by calling it in the main() function as
main() async{
new LinearProgressIndicator(backgroundColor: Colors.lightGreen,);
await _ensureLoggedIn();
runApp(new SpringsterApp());
}
I noticed however that although the Signin prompt with the available Google Accounts on the device happens every time the user has not signed in yet, the last part of the ensureLoggedIn function (if (auth.currentUser == null)) never gets executed, regardless of whether or not the user is signed in.
I noticed this since the print('CURRENTUSER'); is never put out in the console, and additionaly because no new user is created in the Firebase Auth console. Does anyone know why this is happening and how I could maybe fix this?
My Firestore security rules are set to auth only, so no data can be written or read as long as this error occurs. Thanks in advance!
To anyone interested, I was able to sign in as planned with:
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = googleSignIn.currentUser;
if (user == null) user = await googleSignIn.signInSilently();
if (user == null) {
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final FirebaseUser user = await auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await auth.currentUser();
assert(user.uid == currentUser.uid);
}
}
i already initilized firebase app. But still not connect to firebase auth. So i follow from pub dev docs firebase_auth.
ran on emulator.
bool shouldUseFirebaseEmulator = false; // before main()
if (shouldUseFirebaseEmulator) {
await FirebaseAuth.instance.useAuthEmulator('localhost', 9099);
} // inside main()

Resources