I'm implementing a user comments feature, and I'd like to allow users to start writing before asking for authentication to reduce UX friction.
The user flow would be:
The user clicks on the input field, and start typing.
Once done, the user clicks on the submit button, then the system will check if the user is authenticated.
(A) If the user is logged
in, then it will submit the content.
(B) If the user is not logged
in, open a login/signup modal.
(A) If the user has an account, they
choose login methods either email or social login. (B) If the user
is new, they signup for a new account with either email or a social
account.
Once the user is authenticated, the authentication modal will close, and the content will then be submitted. (the user does not need to click on the submit button once again)
I think this is a very common use case, but I cannot find anything on Stackoverflow, or on Google search.
I'm using React with Firebase authentication. My first attempt is to check every second if the user is authenticated.
const submit = async (data) => {
if (!isAuthenticated) {
setOpenAuthDialog(true);
while (!isAuthenticated)
await new Promise((resolve) => setTimeout(resolve, 1000));
}
setDoc(doc(firestore, "comments", postId), data);
setInputField("");
};
However, I don't feel this is the best practice, because it will continue to check even when the user abandons the authentication flow.
I think using callbacks might be the better approach, but the authentication modal and comments are sibling components. I'm not sure if passing the callback function to the sign-in methods is possible.
Please let me know if there is an any better approach to this problem.
One way to do it with callbacks could be:
const submit = async (data) => {
if (!isAuthenticated) {
setOpenAuthDialog(true);
setDataWaitingForSubmission(data)
return;
}
setDoc(doc(firestore, "comments", postId), data);
setInputField("");
}
// This is passed as callback to auth modal
const onAuthSuccess = () => {
if (dataWaitingForSubmission && isAuthenticated) {
submit(dataWaitingForSubmission)
}
}
Related
In firebase, in order to authenticate a use after they click an email link, firebase needs the original clicked email link.
Example
This link redirects you a few times, and brings you to the page needed (currently set to localhost for dev purposes)
Firebase will only accept the email link, and only for one test per email which makes it difficult to diagnose this.
I need a way to fetch the first link clicked (the email link) from the landing page.
I apolagise if this has been answered anywhere else but I tried several combinations of keywords that did not work
export default function Home() {
return (
<>
boilerplate
<button onClick={bigFunction}>Press me to check if the browser has the email saved</button>
</>
)
}
let signinwithemaillink = "https://ticketme.page.link/qbvQ"
function bigFunction()
{
console.log(window.location.href)
if (isSignInWithEmailLink(auth, signinwithemaillink)) {
// 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 = 'example#gmail.com';
}
// The client SDK will parse the code from the link for you.
signInWithEmailLink(auth, email, signinwithemaillink)
.then((result) => {
alert("all good")
console.log(result.user)
// 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) => {
console.log(error.code)
// Some error occurred, you can inspect the code: error.code
// Common errors could be invalid email and invalid or expired OTPs.
});
}
}
As you can tell there is still an incredible amount of comments from the firebase docs, Im just trying to get this to work.
*Amended - I cut out the dynamic link, so the current redirect cycle is as follows:
Email -> Firebase Redirect Link -> Desired Page
so if a user logs into your app, you can check that by
firebase.auth().onAuthStateChanged((user)=>{});
but if the user is already logged in and his user has a property change, how do you see that?
in my case, I have the user verify his email address and when done, he should be able to see a change instantly on his app after verifying his email. So I am using react native, which is pretty much javascript with ES6 syntax in it and I am doing a firebase.auth().onAuthStateChanged(); but its not working, I even have a button on the screen that checks if verified like this:
if (!firebase.auth().currentUser.emailVerified) { firebase.auth().currentUser.sendEmailVerification(); }
else if (firebase.auth().currentUser.emailVerified) { this.setState({ showVerifier: false }); }
but even that isn't working, as if the firebase.auth().currentUser doesn't update if the email is verified, what can be done here?
As far as I understand your question, I would like to give you an idea.
I think onAuthStateChanged() gets triggered only when your Auth State Changes (login, logout) and not when the user properties change.
As they have mentioned in the documentation,
Adds an observer for changes to the user's sign-in state. Prior to
4.0.0, this triggered the observer when users were signed in, signed out, or when the user's ID token changed in situations such as token
expiry or password change. After 4.0.0, the observer is only triggered
on sign-in or sign-out.
function isVerified(){
var user = firebase.auth().currentUser;
if(user != null){
var status = user.emailVerified;
if(status)
{
// Verified
}else{
// Not Verified
}
}
else{
// User didn't login!
}
}
So, you have to manually check it by defining a function like above and you can call this function when the user clicks the button
If you are using react-native-firebase (highly recommended, since it is supports the latest firebase features), you can listen on user changes as stated in this doc
From the doc
Adds a listener to observe changes to the User object. This is a superset of everything from auth#onAuthStateChanged, auth#onIdTokenChanged and user changes. The goal of this method is to provide easier listening to all user changes, such as when credentials are linked and unlinked, without manually having to call User#reload.
onUserChanged(listener: AuthListenerCallback): () => void;
window.addEventListener('storage', e => {
if(e.key === 'access_token' && e.oldValue && !e.newValue) {
store.dispatch(userSignOut());
}
})
If this is a suitable solution, then where(lifecycle event) should i paste this?
The best way to do that is by using the BrodcastChannel feature in Javascript. The Broadcast Channel API allows simple communication between browsing contexts (that is windows, tabs, frames, or iframes) with the same origin (usually pages from the same site).
For example:
// Connecting to a broadcast channel
const userChannel = new BroadcastChannel('user');
function signOut() {
// and after that, we have to broadcast a message to the user channel which user has signed out from the application as below:
userChannel.postMessage({
userId: "", // If the user opened your app in multi-tabs and signed-in with multi accounts, you need to put the userId here to identify which account has signed out exactly
payload: {
type: "SIGN_OUT"
}
});
}
}
So we created the user's BrodcastChannel but we need to observe on sent messages by user's channel and do the right actions by payload type.
userChannel.onmessage = data => {
if(data.payload.type === "SIGN_OUT") {
// As I talked before about multi-accounts, we need to check the current user id with the sent userId by the userChannel and if they were the same, we have to dispatch the userSignOut action.
store.dispatch(userSignOut());
}
}
enter link description here
Try with focus Listener isFocused
Also check
React Navigation emits events to screen components that subscribe to them:
willFocus - the screen will focus
didFocus - the screen focused (if there was a transition, the transition
completed)
willBlur - the screen will be unfocused
didBlur - the screen unfocused (if there was a transition, the transition
completed)
I suppose you should create an action, something like CHECK_CREDENTIALS, and dispatch it on every API call. This way, if you are removing user credentials from localStorage, every tab will be logged out on 1st call.
However, this is something your server should be capable of. When the token is expired/deleted, it is logical that you should get some comprehensive error.
I've been trying to fix the problem for a whole day and couldn't make it work. I'm getting irritated, firebase docs are such a mess it's insane..
So I'm trying to implement email verification on my React app. I wen't with the docs, and google do send the email, I can click it, it's all good.
But, the email verified state doesn't change at all, and believe me I've went through all the stackoverflow topics.
firebase.auth().doSignInWithEmailAndPassword(email, password)
.then(() => {
console.log(firebase.auth().currentUser);
firebase.auth().currentUser.reload()
.then(() => {
console.log(firebase.auth().currentUser);
})
})
So I've found i need to reload the user to see the changes applied, tho they just won't work no matter what I do.
Both console logs return email.verified = false at all times.
I'm hopeless, anyone have any idea on how to make this work?
I was wondering whether me setting up custom domain as a verification link has to do anything with that? I'm testing on localhost, link is linking to live website.
Please help :(
The method you should call is signInWithEmailAndPassword, so remove the "do" in the function call:
firebase.auth().signInWithEmailAndPassword(email, password)
.then((credential) => {
const currentUser = credential.user;
console.log(firebase.auth().currentUser);
firebase.auth().currentUser.reload()
.then(() => {
console.log(firebase.auth().currentUser);
})
}).catch((err) => {console.error("Problem with sign in ", err);}
Use the credential returned from this function and assign its user value to currentUser to validate that the user has been authenticated.
Lastly, remember to add a catch at the end whenever you use then. In this case, err will contain a property err.code that will tell you why the user could not be authenticated.
I had the same problem. You need to reload your current user using a timer and then listen to user changes (not auth changes) in a Streambuilder. As soon as the user clicks the link they will be redirected to your main page.
Use a Streambuilder to listen to changes to the user:
StreamBuilder<User?>(
stream: FirebaseAuth.instance
.userChanges(), // monitor the changes to the user
builder: (ctx, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return LoadingScreen(); // show loading screen while loading
if (snapshot.hasData && !snapshot.data!.emailVerified) {
return VerifyEmailScreen();
} // if not verified show the verify email screen
if (snapshot.hasData) {
return MainScreen();
} // if logged in already show main screen
return OnboardingScreen(); // if not logged in already show the auth screen
},
),
In your VerifyEmailScreen() create a timer in it's initState like this:
void initState() {
Timer.periodic(Duration(seconds: 5), (timer) {
FirebaseAuth.instance.currentUser?.reload();
});
super.initState();
}
I hope it helps your problem.
Cheers
I have an interface in my website where a user can create an "unclaimed" order for another user and send them an invitation. The invitation link will be formatted as /enrollment/orders/:_id, where _id is the id of the order.
One catch is that this can be sent multiple times. On the first time, the user that is invited might not have a password set.
Meteor.publish('enrolled_order', function (token) {
// if user has their password reset, token will also be set for user's account
return Orders.find({
'enrollment.token': token
});
});
Here's the catch: During this publication, I want to check certain aspects of the user record and take different actions instead of publishing it. For security, I believe this will need to be done on the server to work appropriately.
if there is no this.userId, I want to send them to login.
if the user does not have a password set, I want to redirect them to the reset password page.
Is this possible via a meteor publication?
I think you would need to do this in the router. If you're using Iron Router, you could use onBeforeAction to check whether the user is logged in:
Router.route('/enrollment/orders/:_id', {
subscriptions: function() {
return Meteor.subscribe('enrolled_order', this.params._id);
},
onBeforeAction: function() {
if( ! Meteor.userId() ) {
Router.go( 'loginTemplateName' );
}
}
});
You aren't going to want to return the hashed password directly to the client, so maybe setting a variable to say whether the password exists or not might be the best approach?
I can't remember if onBeforeAction happens before waitOn, but you might be able to get away with waitOn instead of subscriptions.