Related
So far in my project, I have set up a basic user management system. However, I'm unable to figure out a way to resend verification link after the user registers.
For example: When the user signs up, createUserWithEmailAndPassword is called which returns a promise. The promise is now resolved using then (or await) to which sendEmailVerification is called. This is all fine.
Note: The above flow is what I currently have implemented to for user management on the client side with Firebase Auth.
However, what if the user happens to delete this email or for whatever reason has no access to it at all. I want to be able to resend the link.
This uses Firebase Admin SDK on the backend and is an example of how to generate the verification email on the server-side. However, it appears that it is used in conjunction with account creation. In addition, it appears that Firebase Auth follows the same set of restrictions.
Not too sure where to go next and was wondering if there are any suitable workarounds.
Thanks.
Add a link to the login page to resend the verification email.
Then, trigger something along these lines:
sendEmailVerification() async {
await FirebaseAuth.instance.currentUser?.sendEmailVerification();
}
Another option is to check during the login process whether the user verified the email. If not, resend it. Along these lines:
signInWithEmailAndPassword(
String email,
String password,
) async {
try {
final credential = await FirebaseAuth.instance
.signInWithEmailAndPassword(email: email, password: password);
if (credential.user!.emailVerified == false) {
await _sendEmailVerification();
return ... // not verified, but email sent
}
return ... // success
} on FirebaseAuthException catch (e) {
return ... // error
} catch (e) {
return ... // error
}
}
The problem described here I think is as follows ( I am facing it as well):
=> some new User enters his Email and Password to create an account
=> we call createUserWithEmailAndPassword(email, password) => account can be found in firebase console under "Authentication" => in the app Auth.currentUser is NOT NULL. This is because acc. to documentatoin of "createUserWithEmailAndPassword" we read:
"#remarks
On successful creation of the user account, this user will also be signed in to your application."
=> Then we call sendEmailVerification(Auth.currentUser) - everything works, User Auth.currentUser gets his Email with verification link.
BUT. What if he does not click this link (maybe it went to spam)? He searcehs it, time passes, he maybe switches off the PC or closes the App. And after some time tries again: opens the App, tries to register again...
=> as he enters again the same E-mail he entered when he tried to register for the first time, our createUserWithEmailAndPassword() will give an error (because the User with such E-mail, even though it is not verified, is already in the database) => Auth.currentUser will be NULL(!!!)
=> if now you try to "REsend the verification E-Mail" with sendEmailVerification(Auth.currentUser) - it will not send anything. Because the user is null.
So, the way out of this situation is to generate verficication link based on the E-mail only and send it somehow to the User via E-mail: https://firebase.google.com/docs/auth/admin/email-action-links#java_2
I have a list of people with all their personal information (name, first name, date of birth, email, etc.).I want to send to each of these people an email with a link allowing them, once clicked, to be directly connected on our website. Without having to type a password.
I followed the Firebase procedure for passwordless authentication:
from the back back in Python. Generate email link for connexion
for the front in Angular Js. Completing signin in a web page
Fireship.io tutorial
But most of the examples don't quite fit my use case.
Most of the examples:
User comes to your website, asks for passwordless authentication, types in his email, (the email is stored in window.location.href)
User receives an email with a link to log in, he clicks on it
User is on your website, logged in (thanks to his email stored in window.location.href).
My use case:
None. I already have the email of my user, so I send him directly the link to connect.
User receives an email with a link to log in, he clicks on it
User is on my website, but has to type his e-mail again in the prompt (because it is obviously not stored in window.location.href).
In my case the window.location.href variable will never be used. And I don't want my user to have to retype his email once the link is clicked. Since I already have his email, why ask him again?
So how can I skip this step? Is there any security risk in doing so?
This is my code so far:
Back:
import firebase_admin
from firebase_admin import auth
from google.cloud import firestore
def create_new_auth(dictionary):
user = auth.create_user(
email=dictionary['email'],
email_verified=True,
phone_number=dictionary['phone'],
password='super_secure_password_007',
display_name=f"{dictionary['firstName']} {dictionary['lastName']}",
disabled=False)
print('Sucessfully created new user: {0}'.format(user.uid))
return user.uid
def create_new_pre_user(db, dictionary, uid):
dictionary = {
'uid': uid,
'email': dictionary['email'],
'lastName': dictionary['lastName'],
'gender': dictionary['gender'],
'birthday': dictionary['birthday'],
'phone': dictionary['phone'],
'firstName': dictionary['firstName']
}
db.collection(u'users').document(uid).set(dictionary)
def main(dictionary):
firebase_admin.initialize_app()
db = firestore.Client()
uid = create_new_auth(dictionary)
create_new_pre_user(db, dictionary, uid)
action_code_settings = auth.ActionCodeSettings(
url=f'http://localhost:4200/login',
handle_code_in_app=True,
ios_bundle_id='com.example.ios',
android_package_name='com.example.android',
android_install_app=True,
android_minimum_version='12',
dynamic_link_domain='magic42.page.link',
)
link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)
if __name__ == '__main__':
dictionary = {
"firstName": "Jone",
"lastName": "Doe",
"birthday": 12345678,
"gender": "male",
"email": "john.doe#gmail.com",
"phone": "+33611223344"
}
main(dictionary)
Front:
private signInWithEmail() {
if (this.authService.isSignInWithEmailLink(window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
let email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// The client SDK will parse the code from the link for you.
this.authService.signInWithEmailLink(email, window.location.href)
.then((result) => {
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
this.router.navigate(['/patient', 'quiz'])
})
.catch((error) => {
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
}
isSignInWithEmailLink(href) {
return this.afAuth.auth.isSignInWithEmailLink(href);
}
signInWithEmailLink(email: string, href: string) {
return this.afAuth.auth.signInWithEmailLink(email, href)
}
EDITS
The problem is that the front has no knowledge of the user email when he first come the our website using the link. There is a way to pass the email information from our server-side to the front but it's in clear in the URL : that's risky and not a good practice according to Firebase itself (link)
Like this:
def main(dictionary):
firebase_admin.initialize_app()
db = firestore.Client()
uid = create_new_auth(dictionary)
create_new_pre_user(db, dictionary, uid)
action_code_settings = auth.ActionCodeSettings(
url=f'http://localhost:4200/login/?email=john.doe#gmail.com',
handle_code_in_app=True,
ios_bundle_id='com.example.ios',
android_package_name='com.example.android',
android_install_app=True,
android_minimum_version='12',
dynamic_link_domain='magic42.page.link',
)
link = auth.generate_sign_in_with_email_link(dictionary['email'], action_code_settings)
So how can I pass the email information from the back to the front so that the user doesn't have to type it again when redirected to my website after clicking to my "magic link" ?
One thing you could do is to create a single-use token on the backend that links to your user's email (or that links to a document in firestore) and have that be in the url. When the user enters the page, make a call to your backend with the token (could be just a simple uuid) and have your backend sign the user in and then expire/remove that token from use.
E.G.
https://yoursite.com/44ed3716-2b8f-4068-a445-b05a8fee17c3
Frontend sends 44ed3716-2b8f-4068-a445-b05a8fee17c3 to backend...backend sees the token, logs them in, then makes that token no longer valid.
Update
To answer your question in the comments below about not needing email link auth anymore through firebase: not necessarily. At that point, you're kind of creating your own email sign-in system (which actually isn't too hard) and somewhat re-inventing the wheel. Adding a token to the url was just a way for you to associate the user with an email without having to actually put the email in the url so that your frontend can know who the user is once your link is clicked. Once the backend sends you the email, you can store it local storage and complete the sign in with firebase normally.
There's no security risk asking the user to type his/her email versus storing it in the window storage, and one could argue that it's actually more secure to do so. That said, how you can go about doing this:
Make sure you have enabled email passwordless authentication.
Using the admin SDK, add each email address to your auth table (though I wouldn't set emailVerified: true - that will happen when they click the magic link and verify themselves on login.
Again using the admin SDK, generate a magic link for each user and send it to them.
On your login page (where the magic link takes them), prompt them for their email address and then use that along with the magic link to authenticate. The sample code provided from Firebase shows you how to do this in the if(!email){ ... } part of the code where it uses a window prompt to collect the user's email in case the user clicked the link on a separate device or the browser didn't/couldn't store the email address.
If you already have the user's email, you can call firebase.auth().sendSignInLinkToEmail with that email in the client-side JavaScript SDK (docs), or generateSignInWithEmailLink in the server-side Node.js SDK (docs). Both calls take the user's email as an argument.
Once the user lands on your site after clicking the link, you can access their profile with an auth state listener like this:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
var uid = user.uid;
var email = user.email;
}
});
EDIT - this question is still unanswered. There was an idea to listen to onIdTokenChanged, however the token is refreshed once every hour, which is not practical solution for me. I posted follow up question here if people can give me a hand that would be grant, because I am sitting on this problem since one week.
I am writing a simple react native app, and I want to show my main page only after user has verified their email. As far as I understand, there is no listener which I can use to listen to event where the user has been verified their email. I am using firebase.auth().onAuthStateChanged((user) => {....}) but the listener onAuthStateChanged has been called after user is logged in or registered in, not after a user has verified their email.
Few places suggested to use firebase.auth().user.reload() so that it will reload the current user and it will pick up the verification status from the database. However, I dont think it is a solution because I dont know when should I reload the current user, i.e. I dont know when the verification link has been clicked. So possible solution to this problem would be:
Send a confirmation 6 digit code to the user, and wait for the user to type it in the app; after the user types it, if the code is the same, I refresh the user. However I dont know how to send custom verification emails with firebase. I checked the documentation, but it is not helpful for me. If someone can point me to example written in react native, or write a small working example with custom email which I can send to the user (again in react native) that would be grant! EDIT - this doesn't seem like possible solution, since Firebase doesn't let you customize the emails
Is it possible solution for me to override onAuthStateChanged listener? S.t. it will listen for changes if the user's email has been verified or not? If that's a good idea can someone point me to the current onAuthStateChanged implementation in react-native, so I can see it as an "inspiration" when overriding? Or if someone has done something similar before, can you show me an example?
I've read several suggestions to use a deep link and to intersept the event when the link has been clicked, but I am not sure how to do this, or even if this is a proper solution to the problem.
Some people suggested to use firebase.auth().user.reload() when the app has been closed and reopened again. This comes from the assumption that when a user has been sent the link, in order for them to click on the link, they need to close the app, and reopen it again. I think this is pretty strong assumption, considering the fact, that they might verify their email via laptop and never close the app, so I dont think this is a good solution either.
Apparently this seems like a well known problem, yet there are not many good solutions. I think best possible solution would be to send 6 digit verification code to the user and after that code has been confirmed, I would reload the current user, pick up the emailVerified field, it will be set to true and then I will show the main screen. However, can someone help me with how do I send custom email in react native and firebase?
If anyone has any other suggestions, please let me know!
You can simply do this by passing a continue url in the actionCodeSettings as below:
const res = await firebase.auth().createUserWithEmailAndPassword(
email,
password
);
await res.user.sendEmailVerification({
url: "https://yoursite.com/continue-url"
});
Is it possible solution for me to override onAuthStateChanged listener? S.t. it will listen for changes if the user's email has been verified or not?
The onAuthStateChanged is called when the user's authentication state changes, so when they go from not being signed in to being signed in or vice versa. The email verification flag being set is not a change in authentication state, so the callback is not called in that case.
You can listen for onIdTokenChanged instead, which fires every time the ID token changes. Since the ID token includes the flag whether the user's email is verified, a callback on onIdTokenChanged will also be called when that changes.
I used #1man solution, just i make sure to delete the interval and unsubscribe from the onAuthStateChanged event:
const onAuthStateChangedUnsubscribe =
firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
// -> Alert Email Verification
await user.sendEmailVerification()
const onIdTokenChangedUnsubscribe = firebase.auth().onIdTokenChanged((user) => {
const unsubscribeSetInterval = setTimeout(() => {
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true)
}, 10000);
if (user && user.emailVerified) {
clearInterval(unsubscribeSetInterval) //delete interval
onAuthStateChangedUnsubscribe() //unsubscribe onAuthStateChanged
// -> Go to your screnn
return onIdTokenChangedUnsubscribe() //unsubscribe onIdTokenChanged
}
})
}
})
So, on my project I made a combination of sendEmailVerification() and reload().
Try it:
await firebase
.auth()
.currentUser.sendEmailVerification(actionCodeSettings)
.then(() => {
//useState used on my loading (user can cancel this loading and exit this task
setTextContent('Waiting for verification. Check your email!\nYou can close this verification and came back later');
const unsubscribeOnUserChanged = firebase
.auth()
.onUserChanged(response => {
const unsubscribeSetInterval = setInterval(() => {//this works as a next in for-like
firebase.auth().currentUser.reload();
}, 30000);
if (response.emailVerified) {
clearInterval(unsubscribeSetInterval); //stop setInterval
setLoading(false); //close loading describes above
navigation.goBack(); //return to parent (in my case to profile)
return unsubscribeOnUserChanged(); //unsubscribe onUserChanged
}
});
})
.catch(error => {
setLoading(false);
setError(true);
errorHandle(error);
});
#3 is a common workflow - Firebase sends the link which, when clicked, opens your app. Your app reads the deep link and handles the payload (email verified). I don't know what language you're using, but you mentioned that you don't know how to do this and it's probably something you'll want to explore.
Your concern in #4 (someone opening the link on a laptop) is only an issue if you allow it to be one. I don't know what language you're using, but when you call the verify email function, you have to pass a url to Firebase which it will use in the email it sends. So your users will be taken wherever you send them. If you send them to a web app or something because you want them to open it on a laptop, then I think your best bet in app would be to have your website (or wherever you're sending them) also write something to a Firestore or RTDB document and have your app listening to that doc for updates.
If the link you pass to Firebase is a deep link to your app, it won't work on their laptop. And in this case, you go back to #3 - read the deep link in your app and handle it early. Also, it's incumbent on you to explain to users how this works, so I'd have my send link confirmation screen explain that they should click the link on the current device.
An alternative would be to have your send link function in-app start a background timer that polls the auth record every few seconds/minutes (whatever your use case), and cancel it when the record is updated or the link expires. I don't love this because email links are valid for 3 days - that's an awful long time to be polling every few seconds in app.
I wanted to do the same thing on the web. I tried the previous three answers and searched a lot but was not able to find the answer. I ended up combining #Frank van Puffelen and #Hermanyo H's solutions into one and it worked for me:
const onAuthStateChangedUnsubscribe = firebase.auth().onAuthStateChanged(async (user) => {
if (user) {
setEmailVerified("Sent");
await user.sendEmailVerification();
const onIdTokenChangedUnsubscribe = firebase.auth().onIdTokenChanged((user) => {
if (user && user.emailVerified) {
setEmailVerified("Verified");
return onIdTokenChangedUnsubscribe(); //unsubscribe
}
setTimeout(() => {
firebase.auth().currentUser.reload();
firebase.auth().currentUser.getIdToken(/* forceRefresh */ true);
}, 10000);
});
}
});
I wrote my own events instead of using onAuthStateChange.
//Write this where you wrote onAuthStateChange event
import auth from '#react-native-firebase/auth';
import {DeviceEventEmitter} from 'react-native';
useEffect(()=>{
let loginListener = DeviceEventEmitter.addListener('#verified_login', params=>{
setUserDetails(auth()._user);
});
return loginListener;
}, []);
Then you can emit this event when you want to allow the user to log in. There's a lot of room for customization here.
await auth().signInWithEmailAndPassword(email, password);
if(auth()._user.emailVerified)
DeviceEventEmitter.emit('#verified_login');
else{
auth()._user.sendEmailVerification()
.then(()=>{
console.log('A verification link has been sent to your email. Please verify to proceed.');
let emailVerificationEventListener = setInterval(async ()=>{
auth().currentUser.reload();
if (auth().currentUser.emailVerified) {
clearInterval(emailVerificationEventListener);
DeviceEventEmitter.emit('#verified_login');
}
}, 1000);
})
.catch(error=>{
console.log(error);
});
}
The api seems to have changed, this worked for me.
auth.idTokenResult.subscribe((result) => {
console.log('onIdTokenChanged');
console.log(result);
})
This issue can be fixed smoothly using firebase dynamic links
when a user requests to authenticate their emails we send a dynamic link with the request:
auth().currentUser.sendEmailVerification({
url: "https://oursite.com/verified-email",
});
when the user clicks on the link in the email he will be redirected to the dynamic link we included above
then we listen to the link and handle it on the client:
dynamicLinks().onLink((link) => {
if (link.url.includes("verified-email")) {
auth().currentUser.reload();
}};
Did you consider the documentation on the Firebase documentation pages?
https://firebase.google.com/docs/auth/web/email-link-auth
Sample code on that page:
import { getAuth, isSignInWithEmailLink, signInWithEmailLink } from "firebase/auth";
// Confirm the link is a sign-in with email link.
const auth = getAuth();
if (isSignInWithEmailLink(auth, window.location.href)) {
// Additional state parameters can also be passed via URL.
// This can be used to continue the user's intended action before triggering
// the sign-in operation.
// Get the email if available. This should be available if the user completes
// the flow on the same device where they started it.
let email = window.localStorage.getItem('emailForSignIn');
if (!email) {
// User opened the link on a different device. To prevent session fixation
// attacks, ask the user to provide the associated email again. For example:
email = window.prompt('Please provide your email for confirmation');
}
// The client SDK will parse the code from the link for you.
signInWithEmailLink(auth, email, window.location.href)
.then((result) => {
// Clear email from storage.
window.localStorage.removeItem('emailForSignIn');
// You can access the new user via result.user
// Additional user info profile not available via:
// result.additionalUserInfo.profile == null
// You can check if the user is new or existing:
// result.additionalUserInfo.isNewUser
})
.catch((error) => {
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
I am trying to add "Apple Sign In" to my Flutter iOS app using the apple_sign_in package.
The code is mostly working. My problem is that the call to _firebaseAuth.signInWithCredential(credential) is creating a firebase account that has a null identifier (please see screenshot below).
One weird thing about this is that when the user selects the option to share their email address when signing up, result.credential.email contains the user's email address after the call to AppleSignIn.performRequests(). So I'm really puzzled as to why the user's email is not being used as the identifier when the account is created in Firebase.
Future<FirebaseUser> signInWithApple() async {
final AuthorizationResult result = await AppleSignIn.performRequests([
AppleIdRequest(requestedScopes: [Scope.email, Scope.fullName])
]);
switch (result.status) {
case AuthorizationStatus.authorized:
final AppleIdCredential appleIdCredential = result.credential;
OAuthProvider oAuthProvider =
new OAuthProvider(providerId: "apple.com");
final AuthCredential credential = oAuthProvider.getCredential(
idToken: String.fromCharCodes(appleIdCredential.identityToken),
accessToken: String.fromCharCodes(
appleIdCredential.authorizationCode),
);
await _firebaseAuth.signInWithCredential(credential);
return _firebaseAuth.currentUser();
break;
case AuthorizationStatus.error:
print('Sign in failed: ${result.error.localizedDescription}');
break;
case AuthorizationStatus.cancelled:
print('User cancelled');
break;
}
return null;
}
I believe this is working as intended;
Apple only returns the full name and email on the first login on the device for your app, it will return null values for these on any login after that. My guess is that you've signed in previously and then tried to sign-in again via a new Firebase Auth user and the Name/Email is no longer available at that point.
For testing purposes, to receive these again; go to your device settings; Settings > Apple ID, iCloud, iTunes & App Store > Password & Security > Apps Using Your Apple ID, tap on your app and tap Stop Using Apple ID. You can now sign-in again and you'll receive the full name and email again.
This is how Apple has designed their API - so it's recommended that you store these elsewhere on your app, e.g. using the shared_preferences plugin - so that you consistently have access to them when required.
The problem is iOS 13.6 in 13.5 I got Name, email, token, id. but in iOS 13.6 only return de user ID. I use the sign_in_with_apple dependency (https://pub.dev/packages/sign_in_with_apple).
If you're going to use firebase to authenticate, get the user's email from firebase user, just do it ...
User? user = await FirebaseAuthOAuth().openSignInFlow("apple.com", ["email"], {"locale": "en"});
And then get the data from the user like...
var email = user.email;
I am using Firebase Authentication with Email and Password
I would like to know if i can 'lookup' a user by email only while also not being signed in as a user
The reason I'd like to do this is, is to simply identify if I am already a user of the system, using an email address only
I looked at this older thread but it appears to be the previous version of Firebase
Is it possible to do this in the current Firebase, or my alternative would be to keep this information available (and open to all?) to find out if a given email is part of my system?
I use fetchProvidersForEmail(email)
and if the result return as empty array then, this email hasn't been use to sign up.
firebase.auth().fetchProvidersForEmail(email)
.then(providers => {
if (providers.length === 0) {
// this email hasn't signed up yet
} else {
// has signed up
}
});
You can look up user information by email:
firebase.auth().getUserByEmail(email)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully fetched user data:', userRecord.toJSON());
})
.catch(function(error) {
console.log('Error fetching user data:', error);
});
I'd like to make things more clear that this method does not exist — one could be looking for it in the firebase client library, in which it has never been available in the first place and it wouldn't be a good idea to have anyway. This method is part of the admin SDK, so in order to call the method, you need to run it on the server, and invoke it from the client. OP didn't scope the question to firebase client library, so my answer is still correct.
Retrieve user data
The new method of creating users with email password returns a value whether the given email address is already in use. See here
import { fetchSignInMethodsForEmail } from 'firebase/auth'
fetchSignInMethodsForEmail(auth, registerEamil).then((result) =>{
console.log("result", result);
if (result.length === 0) {
Navigate("/authentication/select_role/" +
registerEamil)
} else {
Navigate('/')
}
Server side option:
https://cloud.google.com/identity-platform/docs/reference/rest/v1/projects.accounts/lookup
POST https://identitytoolkit.googleapis.com/v1/projects/{targetProjectId}/accounts:lookup
{ "email": ["rodneydangerfield#stackoverflow.kom"] }
As for today 2021 Nov. 18th, there is no way provided by the Firebase SDK to fetch a user by email.