Firebase Auth, how to know new user signed up, rather than existing user sign in? - firebase

My use case is that I want to ask newly signed up users to enrich basic info like their names.
So I was hoping to do it like:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
if (some indicator tells me it is newly signed up user)
{redirect to a form to fill in more info}
} else {
// No user is signed in.
}
});
I checked the doc, and could not find anything related to this...
Thanks for the help in advance.

Since version 4.6.0: https://firebase.google.com/support/release-notes/js#4.6.0
You can get if a user is new or existing in 2 ways:
If you are getting back a UserCredential result, check result.additionalUserInfo.isNewUser
Check firebase.auth().currentUser.metadata.creationTime === firebase.auth().currentUser.metadata.lastSignInTime
Previously you had to do that on your own and keep track of the user using Firebase Realtime Database. When a user signs in, you check if a user with the specified uid exists in the database or not. If the user was not found, it is a new user, you can then add the user to the database. If the user is already in the database then this is a returning existing user. Here is an example in iOS.
Handing Firebase + Facebook login process
Example for using result.additionalUserInfo.isNewUser:
firebase.auth().signInWithPopup(provider).then((result) => {
console.log(result.additionalUserInfo.isNewUser);
});

One thing you can do is do things in the callback function of the signup function, the signup function do return a promise. You can do something like this:
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(function(user) {
//I believe the user variable here is the same as firebase.auth().currentUser
//take the user to some form you want them to fill
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
However, I don't really recommend doing it this way because the client side code can be unreliable. Think about what if a user suddenly disconnect before they can fill the form. Their data will be incomplete in your database. So if you do it this way, do set a flag in your user's profile when they submit the form so that you know who filled detailed information and who didn't.
Another better way to do this is using firebase cloud functions. You can have code like this in your cloud functions. Cloud functions are written in node.js so you don't need to spend time on another language.
exports.someoneSignedUp = functions.auth.user().onCreate(event => {
// you can send them a cloud function to lead them to the detail information form
//or you can send them an welcome email which will also lead them to where you want them to fill detailed information
});
This way is much better because you can safely assume that your cloud functions server will never be down or compromised. For more information about cloud functions you can refer to their doc: https://firebase.google.com/docs/functions/auth-events

You can check the sign-in methods the user has (if any). If there are none, it is a new user.
// Fetch sign in methods (if any)
Auth.auth().fetchSignInMethods(forEmail: userEmail!) { [self] signInMethodsArray, error in
// Check for error and alert user accordingly
if let error = error {
// handle errors
}
// Email accepted.
// Check if new or returning user.
else {
if (signInMethodsArray == nil) {
// New User
}
else {
// Returning User
}
}
}
This is Swift (iOS) code, but the concept is the same across languages.

Related

What is the correct way to use next-auth with Google OAuth and users stored in the database?

I'm new to next-auth, and I'm looking for some help.
I have added the Google OAuth provider, and now when I run signIn("google") function on the frontend, it automatically takes me to the google's login page, and logs me in, somehow, without ever touching my database.
When the google authentication is complete, I need to be able to create a new user in my database, or retrieve the existing one if they have already signed up before (because I need to store all kinds of custom information about the user, not just their email).
And I want to make user's information available on the session object from useSession()hook. Right now I'm seeing some kind of default user info (with name, email, and image field which I didn't define).
When I was using a regular Express server and Passport, the code looked kinda like this:
const googleAuth = new GoogleStrategy(
{
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "/api/v1/profiles/google/callback",
},
async (req, accessToken, refreshToken, profile, done) => {
const existingProfile = await Profile.findOne({ email: profile.emails[0].value })
/* Found user, return him and move along. */
if (existingProfile) return done(null, existingProfile)
/* Haven't found profile with this googleId, create a new one. */
const newProfile = await new Profile({
googleId: profile.id,
email: profile.emails[0].value,
})
newProfile.save()
done(null, newProfile)
}
)
So I would still be creating the users in my database, and retrieving their information on log in, so that I could send it to the client.
Where does this kind of code supposed to go when I'm using the serverless next-auth?
And a second, but kind of related question - what's that default user object that gets provided to me in the session object? The one with the name, email, and image fields that next-auth seems to create for me? How can I make it use the user object I'm returning from my database instead?
(I've done my best to look through the tutorials and examples, but couldn't find one that explains this clearly.)
I don't know if you still need this, but I hope it helps someone:
Oauth kinda mixes up Sign In and Sign Up, so if you want to have Google authentication what you probably want to do is create a callback of the Sign In function in /api/auth/[...nextauth].js, then get the account and profile as parameters and access to its provider.
async signIn({account, profile}) {
if(account.provider === 'google') {
//check if user is in your database
if(user NOT in DB) {
//add your user in DB here with profile data (profile.email, profile.name)
}
return true
}
You always want to return true since you always want to log in independently if it is in your DB or not.
Regarding the session object, you can also add a callback and access to the default session (that you can modify), token and user. Here you can retrieve all information you want from your database, add it to the session object and return it.
async session({ session, token, user }) {
const newData = DB.find(...).data
session.newfield = newInfo
return session
}

Sending Verification email to existing users

I am working on a web app with an existing user base. Email verification was not initially implemented in the sign in flow.
I have successfully added code for sending verification email for all new sign ups but I also wanted to make a small page (or modal) where current users would be shown a button that would send the verification link to their inbox
The current sign up flow where I created the user with createUserWithEmailAndPassword I was able to get access to the user.user.sendEmailVerification method to do so, but cannot find any way to access this method to implement the feature for existing users.
Is there a way to access the sendEmailVerification method after the user has been created?
I am assuming that it would be available within the onAuthStateChange trigger but implementing that would lead to a bad UX (as I do not want to prompt the users everytime they login)
Edit:
I know the documentation states that we can use the firebase.auth().currentUser to get the current user but that, for some reason did not work.
Also, I found references online suggesting to no longer use that method and they mentioned to use the onAuthStateChange method instead, which is why I was looking into that approach
You can try this method:
const btnVerifyEmail = document.getElementById("btn-verify-id")
btnVerifyEmail.onclick = function () {
const user = firebase.auth().currentUser;
user.sendEmailVerification().then(function() {
// Email sent.
console.log("Email Sent")
}).catch(function(error) {
// An error happened.
console.log(error)
});
}
It's mentioned in the documentation right here
The sendEmailVerification() should not be called in the onAuthStateChanged event because it would blast out an email on every page load if the user's email isn't verified.
You should instead display a notification on the page if User.emailVerified is false that contains a link to send the user an email.
Here's a working example:
// On page load watch for auth state changes
firebase.auth().onAuthStateChanged(function(user) {
// If the user is logged in
if (user) {
// If the user's email isn't verified
if (!user.emailVerified) {
// Show the notification bar that informs the user that they need to validate
// their email by clicking a link. Let's pretend the link looks like this:
// Send me a verification email
showNotification();
}
}
});
// Function attached to your link's onclick event
function sendEmailVerification() {
// Retrieve the current user
const user = firebase.auth().currentUser;
// If user's email is already verified, exit
if (user.emailVerified) {
return;
}
// Tell Firebase to send the verification email and discard the promise
user.sendEmailVerification().then().catch();
}
Dharmaraj's answer is good but this is a full example.

How to determine whether it's a SignUp or SignIn in Passwordless Auth from Firebase? [duplicate]

My use case is that I want to ask newly signed up users to enrich basic info like their names.
So I was hoping to do it like:
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
// User is signed in.
if (some indicator tells me it is newly signed up user)
{redirect to a form to fill in more info}
} else {
// No user is signed in.
}
});
I checked the doc, and could not find anything related to this...
Thanks for the help in advance.
Since version 4.6.0: https://firebase.google.com/support/release-notes/js#4.6.0
You can get if a user is new or existing in 2 ways:
If you are getting back a UserCredential result, check result.additionalUserInfo.isNewUser
Check firebase.auth().currentUser.metadata.creationTime === firebase.auth().currentUser.metadata.lastSignInTime
Previously you had to do that on your own and keep track of the user using Firebase Realtime Database. When a user signs in, you check if a user with the specified uid exists in the database or not. If the user was not found, it is a new user, you can then add the user to the database. If the user is already in the database then this is a returning existing user. Here is an example in iOS.
Handing Firebase + Facebook login process
Example for using result.additionalUserInfo.isNewUser:
firebase.auth().signInWithPopup(provider).then((result) => {
console.log(result.additionalUserInfo.isNewUser);
});
One thing you can do is do things in the callback function of the signup function, the signup function do return a promise. You can do something like this:
firebase.auth().createUserWithEmailAndPassword(email, password)
.then(function(user) {
//I believe the user variable here is the same as firebase.auth().currentUser
//take the user to some form you want them to fill
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// ...
});
However, I don't really recommend doing it this way because the client side code can be unreliable. Think about what if a user suddenly disconnect before they can fill the form. Their data will be incomplete in your database. So if you do it this way, do set a flag in your user's profile when they submit the form so that you know who filled detailed information and who didn't.
Another better way to do this is using firebase cloud functions. You can have code like this in your cloud functions. Cloud functions are written in node.js so you don't need to spend time on another language.
exports.someoneSignedUp = functions.auth.user().onCreate(event => {
// you can send them a cloud function to lead them to the detail information form
//or you can send them an welcome email which will also lead them to where you want them to fill detailed information
});
This way is much better because you can safely assume that your cloud functions server will never be down or compromised. For more information about cloud functions you can refer to their doc: https://firebase.google.com/docs/functions/auth-events
You can check the sign-in methods the user has (if any). If there are none, it is a new user.
// Fetch sign in methods (if any)
Auth.auth().fetchSignInMethods(forEmail: userEmail!) { [self] signInMethodsArray, error in
// Check for error and alert user accordingly
if let error = error {
// handle errors
}
// Email accepted.
// Check if new or returning user.
else {
if (signInMethodsArray == nil) {
// New User
}
else {
// Returning User
}
}
}
This is Swift (iOS) code, but the concept is the same across languages.

Flutter - How to add Firebase-Auth user credentials to new records (FireStore documents)?

I'm trying to create a simple Crud app with Flutter and Firebase which the records (documents created in FireStore) are related to the user who has been Authenticated. Therefore the Crud functions will only be performed by the user who created the record. IE a user will only be able able to edit/update/delete the records they added in the first place.
I have the firebase_auth and crud functions working nicely with firestore. the issues i'm have is with relating the two. I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not). I have created a separate function for simply returning the current user's email as it's being added to the firestore document. The problem is the first time i add a record the user email returns null, If i submit the form again it starts working fine.
String _userEmail;
_getUserAuthEmail() {
FirebaseAuth.instance.currentUser().then((user){
setState((){this._userEmail = user.email;});
});
return this._userEmail;
}
Which is being called from the onPressed event
onPressed: () {
crudObj.addData({
'itemName': this.itemName,
'userEmail': _getUserAuthEmail(),
}).then((result) {
dialogTrigger(context);
}).catchError((e) {
print(e);
});
},
As i'm just starting out please let me know if there is a better approach. Cheers.
You are getting null because you are not waiting for the currentUser method to settle. Change the _getUserEmail method like this:
String _userEmail;
_getUserAuthEmail() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
setState(() {
_userEmail = user.email;
});
return this._userEmail;
}
Also, about this
"I have chosen to use the users email and the unique identifier (i'm not sure if it's better to use the auto generated user id or not)."
I suggest you using the user's uid for saving user related stuff.

meteor-shopify User Creation/ Login after Auth callback

Assuming I want to create users upon authorizing the app, how would I grab their email during the onAuth callback...? Looks like the callback assumes the user is already logged in. Am I thinking about it correctly?
I noticed when installing the Fishbowl Prizes app, after auth I can click on the accounts tab and see that all my account info is pre-populated from my shopify store account (name, email, address, etc).
I'm not sure if I should go by the title or the content of the post in terms of answering your question, so I'll provide a very simple example of how to get the info from the API and do something with it here.
I have provided a more in depth answer related specifically to grabbing the details from the API for user account creation here: https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630
Looks like the callback assumes the user is already logged in.
The userId param is undefined if there is no user. If your onAuth operations don't need to do anything with the user, you can just leave it out of the params. In your case you'll just want to handle it conditionally using an if/else block:
if(!userId){
// do stuff
} else {
// do other stuff
}
On to the example of grabbing those details from the API:
All the prepopulated information you are seeing is available from the Shopify API in the shop object. You already have the access token when onAuth callbacks are fired, so you can just grab it from the API immediately after you have inserted the shop's Keyset.
For the sake of simplicity, in this example we'll assume the user already exists and is logged in. In your server-side onAuth callback (after you have inserted the keyset) you can do something like this to add those fields to the user's profile object:
Shopify.onAuth(function(access_token, authConfig, userId) {
var shopUUID = uuid.new(); // Not secure to name keyset same as the shop!
Shopify.addKeyset(shopUUID, {
access_token: access_token
});
var api = new Shopify.API({
shop: authConfig.shop,
keyset: shopUUID
});
// get the Shop object from the API
var shopObj = api.getShop();
var userInfo = {
'profile.name': shopObj.shop_owner,
'profile.email': shopObj.email,
'profile.phone': shopObj.phone,
'profile.shopName': shopObj.name
};
Meteor.users.update({_id: userId}, {$set: userInfo})
});
Then you can use them in templates like this:
{{currentUser.profile.name}} or {{currentUser.profile.email}}
Or in functions like so:
var realName = Meteor.user().profile.name
or
var userEmail = Meteor.user().profile.email etc
For a more about using this data for user creation, see my explanation here:
https://github.com/froatsnook/meteor-shopify/issues/15#issuecomment-177413630

Resources