How to get Facebook Email from flutter_facebook_login in Firebase? - firebase

I am using flutter_facebook_login plugin(3.0.0). But my Facebook email doesn't appear inside my Firebase user identifier column; instead, I see this "--". Please help!
Future loginWithFacebook() async {
FacebookLogin facebookLogin = FacebookLogin();
final result = await facebookLogin.logIn(['email', 'public_profile']);
//result.accessToken.
final token = result.accessToken.token;
print('Facebook token userID : ${result.accessToken.permissions}');
final graphResponse = await http.get(
'https://graph.facebook.com/v2.12/me?fields=name,first_name,last_name,email&access_token=${token}');
final profile = jsonDecode(graphResponse.body);
print(profile);
if (result.status == FacebookLoginStatus.loggedIn) {
final credential = FacebookAuthProvider.getCredential(accessToken: token);
FirebaseUser fbUser = (await _auth.signInWithCredential(credential)).user;
//print('Our credential is : $credential');
print('Facebook firebase user ${fbUser.}');
}
return _userFromFacebookLogin(profile);
}
}

Possible Points.
Some Facebook accounts are created using Mobile numbers, so whenever we request for email address we get an empty string.
Email was set to "--" on firebase auth due to missing permission to read email, which fixed by:
final FacebookLoginResult facebookLoginResult = await facebook_Login.logIn(['email', 'public_profile']);
After reading post in firebase-talk google group here https://groups.google.com/forum/#!topic/firebase-talk/gPGNq-IkTLo, I found out the answer. The issue was happened because I'm using "Allow creation of multiple accounts with the same email address" in Firebase Auth sign-in method.
So I change the option into: "Prevent creation of multiple accounts with the same email address" can it's working properly now. It's simple as that. It's true I need more logic to merge accounts having the same email address, but it's okay.
Maybe everyone else having the same issue, can also try this, and hopefully it's solved as well.

Related

What Sign in method to use best?

We are having a flutter app (ios, android, web), where users are signed in via username & password.
We are also using google firebase since its powerful and easy to integrate.
The username and password mainly belongs to the website where we are gathering data at. (As example - If they use the website without the app, and they change the password, after that he wont be able to login to the app)
Now the mentionned websites host is giving us API access, login via OpenId to get the access token for the API. Because we are a safety risk since we store the passwort of the users too!
For the API access we dont really need to store Username and password of the user, since they are redundant anyway. But if we want to add a feature (for example message sending or further data storage) we need to have the user signed in into firebase.
Upt to now we are using for (first) signin the following snippet:
firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
and for already signed in users :
firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
Notice that similar credentials are also using to login on the API. (Since the user is there already registered)
How can we login on firebase with said information without asking twice for a password ond username (once for us, and once for the API) ?
We already tried :
await firebaseAuth.signInWithCustomToken(token)
with the jwl token from the OpenId, of course it did not work because the token did not contain the uid reference.
SOLUTION
Create a Firebase Cloud Function just like described in Firebase Cloud Functions.
Be aware that if you want to create a customtoken, the cloud functions need rights. On initializeApp(..)
admin.initializeApp({
serviceAccountId: '{App_Name}#appspot.gserviceaccount.com',
});
So the correct service account has to be selected, you also have to give him the rights to generate tokens. (See => Stackoverflow Question
The Cloud Function does then look the following way :
export const functionName= functions.https.onRequest(async (request, response) => {
const id = request.query.id;
const passcode = request.query.passcode; // not really needed
// add other passcodes for different authentications
if (passcode == "{COMPARE SOMETHING}") {
await admin.auth().createCustomToken(id).then((customToken) => {
response.status(200).send({
'id': id,
'customToken': customToken
});
}).catch((error) => {
response.status(500).send({
'ErrorMessage': "No token could be generated",
"Error": error
});
});
}
else {
response.status(500).send({
'ErrorMessage': "Passcode wrong"
});
}
});
On the other hand we have the code on the mobile app :
// Get JWT Token
Map<String, dynamic> jwtpayload =
Jwt.parseJwt(response_decoded['id_token']); // use import 'package:jwt_decode/jwt_decode.dart';
final queryParameters = {
'id': jwtpayload ['sub'],
'passcode': 'APassCode',
};
final uri = Uri.https('us-central1-{yourApp}.cloudfunctions.net',
'/{functionName}', queryParameters);
final cloud_function_api_call = await client.post(uri);
var decoded_cloud_function_api_call =
jsonDecode(cloud_function_api_call.body);
And at the end :
await firebaseAuth.signInWithCustomToken(
decoded_cloud_function_api_call['customToken']);
I hope it helps others facing a similar issue.

How to make Flutter authentification just over Firestore (without Firebase Auth)

I have to create a manual Registration and Sign in over Firestore without using Firebase Auth.
The reason for that requrirement is once when user wants to reset his password, first of all I have to sent him a confirmation code (MD5) on his email address and when he receives the code, above new password that he should enter, he must paste that code.
I'll sent him a code on email and in the same time, that code will be also in Firestore from which later on I will compare (code is correct or not).
By the end, (if entered code is correct, his new password will overwrite previous stored password in his document). Documents are named by email address which means that email should be unique.
Here is the code that I'm using for registration:
void onPressedRegister(BuildContext context, String fullName, String email,
String password, String phoneNumber, dynamic formKey) {
if (formKey.currentState.validate()) {
db.collection("firestoreUsers").document(email).setData({
'fullName': fullName,
'email': email,
'password': password,
'phoneNumber': phoneNumber
});
Now I have a problem with Sign in because I can't check is user stored in database or not. Here is what I wrote:
Future<bool> signInOverFirestore(String email, String password) async {
db
.collection('firestoreUsers')
.where('email', isEqualTo: email)
.getDocuments();
return true;
}
So is it possible to make sign in like this and also update that user later on when he enter correct code from his email, or not?
If you're not using Firebase Auth with Firestore and security rules, it's basically impossible to have secure per-user data. What you have now is storing plaintext email addresses and passwords for everyone in the world to see. All someone has to do is use the Firestore REST API to query the entire firestoreUsers collection to see everything.
According to my understanding, you want to make Firestore data public (access/read without authentication) check if some document with a value and if that value doesn't exist, you want to create a new document. In order to do that you can try to fetch the document which is equal to your value(email) and catch the error or listen if it is successful. You may accomplish it with the following example.
_db
.collection('path')
.where('field', isEqualTo: '')
.getDocuments()
.then((value) {
print('User on Firestore : $value');
}).catchError((e) {
print('Error no document found');
});

Firebase Id Token from Flutter App is not verified on the REST server

I want support social login with firebase auth. But I want to keep user's detailed profiles in my own RDB .
I thought that after user signed in with social account I could get encoded token which contains user's id and email.
And then if I send the token to the REST server (I built it with spring boot), then the server can decode it to load user's detailed profile from RDB.
I followed the guide from this article.
https://blog.codemagic.io/firebase-authentication-google-sign-in-using-flutter/
I can see sign in process successfully done and the id token printed at the console.
the flutter(dart) code is below.
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;
name = user.displayName;
email = user.email;
imageUrl = user.photoUrl;
final FirebaseUser currentUser = await _auth.currentUser();
await currentUser.getIdToken().then((value) => print(value.token));
I copied the token into the server modult to test it could be decoded with no problem.
I added firebase-admin dependency. and run it with java code below.
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.getApplicationDefault())
.setDatabaseUrl("https://test-firebase-auth-token.firebaseio.com")
.build();
FirebaseApp.initializeApp(options);
String firebaseToken = "eyJhbGciOiJSUzI1NiIsImtpZCI6ImQ1OThkYjVjZ..."; // actually it's long string.
FirebaseToken decodedToken = FirebaseAuth.getInstance().verifyIdToken(firebaseToken);
System.out.println(decodedToken.getUid());
System.out.println(decodedToken.getEmail());
And I got an error.
Exception in thread "main" com.google.firebase.auth.FirebaseAuthException: Failed to verify the signature of Firebase ID token. See https://firebase.google.com/docs/auth/admin/verify-id-tokens for details on how to retrieve an ID token.
at com.google.firebase.auth.FirebaseTokenVerifierImpl.checkSignature(FirebaseTokenVerifierImpl.java:154)
at com.google.firebase.auth.FirebaseTokenVerifierImpl.verifyToken(FirebaseTokenVerifierImpl.java:92)
at com.google.firebase.auth.FirebaseAuth$4.execute(FirebaseAuth.java:426)
at com.google.firebase.auth.FirebaseAuth$4.execute(FirebaseAuth.java:423)
at com.google.firebase.internal.CallableOperation.call(CallableOperation.java:36)
at com.google.firebase.auth.FirebaseAuth.verifyIdToken(FirebaseAuth.java:388)
at com.google.firebase.auth.FirebaseAuth.verifyIdToken(FirebaseAuth.java:362)
at com.example.demo.DemoApplicationTests.main(DemoApplicationTests.java:36)
I tried it again with firebase-server-sdk dependency instead of firebase-admin.
And I got an another error message. (seems that it caused by same reason with above error message)
Caused by: com.google.firebase.auth.FirebaseAuthException: Token isn't signed by a valid public key
at com.google.firebase.auth.internal.FirebaseTokenVerifier.verifyTokenAndSignature(FirebaseTokenVerifier.java:61)
at com.google.firebase.auth.FirebaseAuth$1.call(FirebaseAuth.java:146)
at com.google.firebase.auth.FirebaseAuth$1.call(FirebaseAuth.java:140)
at com.google.firebase.tasks.Tasks$1.run(Tasks.java:63)
I checked the token on this site.
https://jwt.io/
I wonder the token is really signed with a (private) key?
Then how jwt.io site decode it without knowing the key?
It seems like it is just encoded with open algorithm like base64. Isn't it?
If so... the token is not secure any more.
What's the problem with my application. or Do I have miss-conception on security?
Any comment will be welcomed. Thanks...
As answered here, problem on print method. It has limited to ~1024 symbols.
You can print like this:
final idToken = await firebaseCredential.user!.getIdToken(true);
print(idToken.substring(0, 1000));
print(idToken.substring(1000));

What is the logic behind this authentication in Firebase for Flutter?

I'm following the Firebase for Flutter Codelab and on the 8th step there's this _ensureLoggedIn() function:
final _googleSignIn = new GoogleSignIn();
final _auth = FirebaseAuth.instance;
Future<Null> _ensureLoggedIn() async {
GoogleSignInAccount user = _googleSignIn.currentUser;
if (user == null)
user = await _googleSignIn.signInSilently();
if (user == null) {
await _googleSignIn.signIn();
analytics.logLogin();
}
if (await auth.currentUser() == null) {
GoogleSignInAuthentication credentials =
await _googleSignIn.currentUser.authentication;
await auth.signInWithGoogle(
idToken: credentials.idToken,
accessToken: credentials.accessToken,
);
}
}
As a newbie to both Flutter and Firebase frameworks, i'm really struggling to understand the logic behind it: First we attempt to log the user with the GoogleSignIn package, and then regardless of what we will have in user we try to auth the user again with FirebaseAuth, which in turn will also use the GoogleSignIn.
Could you explain me why we do both?
My target is to have two separate screens for users who open my app - one for unauthorized/anonymous (which will have Log In and Register options), and one for authorized users that will see the normal app interface.
The login example in that Codelab seems poorly written, as the user can potentially cancel the non-silent signIn() and then googleSignIn.currentUser will be null when they try to access googleSignIn.currentUser.authentication. I think a better way to handle it is trigger the Google sign in and handle the Firebase auth in in the googleSignIn.onAuthStateChanged listener.
As for why both are used in that example: If you want to authenticate a user on Firebase with a Google account you have to provide an idToken and accessToken, which must be obtained from a valid Google login. So first, you have them sign in to their Google account (via googleSignIn) and use the tokens from that to authenticate with Firebase (via auth).
Using googleSignIn is only required if you want to authenticate with Firebase using a Google account; you can also use Firebase Auth with a username/password combination (which requires that the account be created on Firebase first) or with a token from a Facebook login, or some other OAuth token.

Get Identifier field from Firebase Auth Console

I'm trying to get the email from the user that's currently authenticated using the Facebook Firebase provider. The email is listed under the Identifier field inside the Project's Firebase Authentication Console:
However when I invoke firebase.auth().currentUser the user information loads, however the email field is null. Any ideas on how to get the Identifier (which is where I see the email address) from Firebase? Is this even possible?
Below is the code I'm using:
componentDidMount() {
let user = firebase.auth().currentUser;
let name, email, photoUrl, uid, emailVerified;
if (user !== null) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid;
console.log(name, email, photoUrl, emailVerified, uid);
}
}
Note: Prevent creation of multiple accounts with the same email address is enabled in Firebase. Also, Facebook API permissions are set to ['public_profile', 'email']
After some testing and debugging I found that the email field will be populated if you're using a regular Firebase Email/Password Sign In method. However, if you're using another Sign In provider method such as Facebook, the email field will appear null (not sure why).
Further inspection of the user object revealed a providerData property.
It's an array that contains all the provider information (including the email address):
So, I updated my code to accommodate this:
componentDidMount() {
let user = firebase.auth().currentUser;
let name, email, photoUrl, uid, emailVerified;
if (user) {
name = user.displayName;
email = user.email;
photoUrl = user.photoURL;
emailVerified = user.emailVerified;
uid = user.uid;
if (!email) {
email = user.providerData[0].email;
}
console.log(name, email, photoUrl, emailVerified, uid);
}
}
In my case, the getEmail() method always returns data for three sign-in possibilities (if user gave authorization to my app to show/use email): Sign in with Email, Sign in with Google, Sign in with Facebook.
FirebaseUser user = FirebaseAuth.getInstance().getCurrentUser();
Log.v(TAG,"user.getEmail():"+user.getEmail());
if (user.getEmail() == null){
// User did not authorize the app to show/user email
}
else {
Log.v(TAG,"user.getEmail():"+user.getEmail());
}

Resources