today is good day [duplicate] - firebase

Precondition
$npm install --save firebase#4.11.0
issue
I'm using firebase authentication on my web application.
In my app, I implemented onAuthStateChanged for client side js like below.
firebase.auth().onAuthStateChanged((user) => {
if(user) {
//logged in
} else {
//do sth
}
});
After login, I confirmed this method will return actual user obj, but if I refresh the page, then user might be null.
Curiously, sometimes user won't be null.
I'm afraid there are some limitation of calling onAuthStateChanged, but currently I have no idea.
How should I deal with this issue?
update
Let me share my minimal example.
My app is working with express.js.
There are two URLs like below.
/login
/main
In the login page, I implemented authentication method.
If the login is successfully finished, then user will be redirected to '/main'.
//login.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
var provider = new firebase.auth.GoogleAuthProvider();
firebase.auth().signInWithPopup(provider)
.then((result) => {
return result.user.getIdToken(true);
}).then((idToken) => {
if(idToken) {
location.href = '/main';
}
});
In the main page, there is no login method.
main.js is only checking whether user is logged in.
//main.js
import firebase from 'firebase';
var config = {...};
firebase.initializeApp(config);
firebase.auth().onAuthStateChanged((user) => {
if (user) {
//initialize main page.
} else {
location.href = '/login';
}
}
I think login status is stored on LocalStorage of web browser.
This means that, after finishing loading of main.js, onAuthStateChanged will be automatically fired with user information, but not working as I expected.
I'm sure that persistence of login information is correct because official document says the default setting is LOCAL for web client.
https://firebase.google.com/docs/auth/web/auth-state-persistence
my question
Should I implement onAuthStateChanged with another way?
How can I ensure user is logged in after reload?
e.g.
import $ from 'jquery';
$(document).on('ready', () => {
onAuthStateChanged((user) => {...});
});
Or could you show me the correct way?
Workaround
I decided to remove session and set redirection to login page if null is returned. This is not a solution, but a workaround currently...

You're not calling onAuthStateChanged. Instead you're telling Firebase to call you when the authentication state changes, which may happen a few times when the page is being re-loaded
When a page is getting loaded and there was previously a user signed in, the auth state may change a few times, while the client is figuring out if the user's authentication state it still valid. For that reason, you may see a call with no user before seeing the final call with the actual signed in user.

The fact it's sometimes null and sometimes not null likely points to an async problem. Are you making the check in the if statement above? All references to the user should be within the callback. If that all checks out, maybe check that authentication is being properly initiated.

onAuthStateChanged is an observer as stated in firebase docs, which gets triggered when the auth state is changed like user signed in, signed out, pwd change. To check if user is logged in or not you should use firebase.auth().currentUser which will give you the current logged in user. As you said your state is local firebase.auth().currentUser will always give you user unless user is signed out.

Related

Missing initial state in WebAuthenticator login attempt

working on a .NET MAUI app and am trying to implement Firebase Authentication with the help of WebAuthenticator in MAUI. I get to the login form in a browser, but after logging in get the error
Unable to process request due to missing initial state. This may happen if browser sessionStorage is inaccessible or accidentally cleared.
This is the code that calls the authenticator
await client.SignInWithRedirectAsync(FirebaseProviderType.Google, async uri =>
{
var options = new WebAuthenticatorOptions
{
Url = new Uri(uri),
CallbackUrl = new Uri("com.companyname.myappname://callback/"),
PrefersEphemeralWebBrowserSession= true
};
var res = await WebAuthenticator.Default.AuthenticateAsync(options);
});
I think the problem could be the callback URL, but I'm not sure how to write it differently since I'm not using a backend API. Does anyone have any suggestions?
Thanks!
P.S. This happens with bost Firebase Google auth and Facebook login
You can try to clear the chrome browser data and reload the page to see if it works. This is a known problem of firebase. You can continue to follow up this github iissue: Unableto process request due to missing initial state.

When to set Firebase auth state persistence

according to the guide, the firebase auth state persistance is set before the actual login method is called:
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.SESSION)
.then(function() {
// Existing and future Auth states are now persisted in the current
// session only. Closing the window would clear any existing state even
// if a user forgets to sign out.
// ...
// New sign-in will be persisted with session persistence.
return firebase.auth().signInWithEmailAndPassword(email, password);
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
});
See https://firebase.google.com/docs/auth/web/auth-state-persistence
However, shouldn't it be the other way around? Shouldn't I first ensure that the login was successful and then attempt to set the persistence?
In the suggested approach, a user could have like 10 wrong login attempts, and everytime he would request firebase to set the persistence, even if the login was not successful.
It would be the same for Signup for instance. Is there an actual way to set the firebase auth persistance persistantly to SESSION or NONE by default?
Actually, it is quite flexible. You can set it once and the last setting will always be applied. You don't need to that each time. It will remember the last persistence setting as long as you don't reload the page.
Also you have the ability to change the persistence after sign in.
So if the user signs in and the default persistence was used and then you set persistence to SESSION, the user state will be converted to SESSION.
In angular project the following code works.
/**
* constructor
* #param fireAuthService service for checking authentication in firebase cloud
*/
constructor(private fireAuthService: AngularFireAuth) {
this.fireAuthService.setPersistence('session');
}

How to logout user if he changes his Google account password?

I am successfuly able to login using Google but now I want to logout the user when he changes his Google account password.
I have tried the code below:
OptionalPendingResult<GoogleSignInResult> opr = Auth.GoogleSignInApi.silentSignIn(mGoogleApiClient);
if (opr.isDone()) {
// If the user's cached credentials are valid, the OptionalPendingResult will be "done"
// and the GoogleSignInResult will be available instantly.
Log.d("TAG", "Got cached sign-in");
GoogleSignInResult result = opr.get();
} else {
// If the user has not previously signed in on this device or the sign-in has expired,
// this asynchronous branch will attempt to sign in the user silently. Cross-device
// single sign-on will occur in this branch.
opr.setResultCallback(new ResultCallback<GoogleSignInResult>() {
#Override
public void onResult(GoogleSignInResult googleSignInResult) {
}
});
}
...but even after changing password and revoking access, I was able to silently login. I can't find anything else.
Can someone help me to detect if user has changed his password, so I can log him out?
I found an issue reported for the same.
https://github.com/googlesamples/google-services/issues/196
According to them it is expected behavior and you might need to explicitly call signOut or revokeAccess from your application to achieve that.

New user email verification [duplicate]

Question says it all. In Firebase, how do I confirm email when a user creates an account, or, for that matter, do password reset via email.
I could ask more broadly: is there any way to send emails out from Firebase? E.g. notifications, etc. This isn't the kind of thing you would usually do client-side.
Update
Note that this was never a very secure way of handling email verification, and since Firebase now supports email verification, it should probably be used instead.
Original answer
I solved the email verification using the password reset feature.
On account creation I give the user a temporary (randomly generated) password. I then trigger a password reset which will send an email to the user with a link. The link will allow the user to set a new password.
To generate a random password you can use code similar to this:
function () {
var possibleChars = ['abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789!?_-'];
var password = '';
for(var i = 0; i < 16; i += 1) {
password += possibleChars[Math.floor(Math.random() * possibleChars.length)];
}
return password;
}
Note that this is happening on the client, so a malicious user could tamper with your logic.
This would need to be done outside of firebase. I store users at /users/ and keep a status on them (PENDING, ACTIVE, DELETED). I have a small service that monitors users of a PENDING status and sends out a confirmation email. Which has a link to a webservice I've created to update the user status to ACTIVE.
[Engineer at Firebase - Update 2014-01-27]
Firebase Simple Login now supports password resets for email / password authentication.
Each of the Simple Login client libraries has been given a new method for generating password reset emails for the specified email address - sendPasswordResetEmail() on the Web and Android, and sendPasswordResetForEmail() on iOS.
This e-mail will contain a temporary token that the user may use to log into their account and update their credentials. This token will expire after 24 hours or when the user changes their password, whichever occurs first.
Also note that Firebase Simple Login enables full configuration of the email template as well as the sending address (including whitelabel email from your domain for paid accounts).
To get access to this feature, you'll need to update your client library to a version of v1.2.0 or greater. To grab the latest version, check out https://www.firebase.com/docs/downloads.html.
Also, check out https://www.firebase.com/docs/security/simple-login-email-password.html for the latest Firebase Simple Login - Web Client docs.
As at 2016 July, you might not have to use the reset link etc. Just use the sendEmailVerification() and applyActionCode functions:
In short, below is basically how you'll approach this, in AngularJS:
// thecontroller.js
$scope.sendVerifyEmail = function() {
console.log('Email sent, whaaaaam!');
currentAuth.sendEmailVerification();
}
// where currentAuth came from something like this:
// routerconfig
....
templateUrl: 'bla.html',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn() // this throws an AUTH_REQUIRED broadcast
}]
}
...
// intercept the broadcast like so if you want:
....
$rootScope.$on("$stateChangeError", function(event, toState, toParams, fromState, fromParams, error) {
if (error === "AUTH_REQUIRED") {
$state.go('login', { toWhere: toState });
}
});
....
// So user receives the email. How do you process the `oobCode` that returns?
// You may do something like this:
// catch the url with its mode and oobCode
.state('emailVerify', {
url: '/verify-email?mode&oobCode',
templateUrl: 'auth/verify-email.html',
controller: 'emailVerifyController',
resolve: {
currentAuth:['Auth', function(Auth) {
return Auth.$requireSignIn()
}]
}
})
// Then digest like so where each term is what they sound like:
.controller('emailVerifyController', ['$scope', '$stateParams', 'currentAuth', 'DatabaseRef',
function($scope, $stateParams, currentAuth, DatabaseRef) {
console.log(currentAuth);
$scope.doVerify = function() {
firebase.auth()
.applyActionCode($stateParams.oobCode)
.then(function(data) {
// change emailVerified for logged in User
console.log('Verification happened');
})
.catch(function(error) {
$scope.error = error.message;
console.log(error.message, error.reason)
})
};
}
])
And ooh, with the above approach, I do not think there's any need keeping the verification of your user's email in your user data area. The applyActionCode changes the emailVerified to true from false.
Email verification is important when users sign in with the local account. However, for many social authentications, the incoming emailVerified will be true already.
Explained more in the article Email Verification with Firebase 3.0 SDK
What I did to work around this was use Zapier which has a built in API for firebase. It checks a location for added child elements. Then it takes the mail address and a verification url from the data of new nodes and sends them forwards. The url points back to my angular app, which sets the user email as verified.
As I host my app files in firebase, I don't need have to take care of any servers or processes doing polling in the background.
There is a delay, but as I don't block users before verifying mails it's ok. Zapier has a free tier and since I don't have much traffic it's a decent workaround for time being.
The new Firebase SDK v3 appears to support email address verification, see here (put your own project id in the link) but it doesn't appear to be documented yet.
I have asked the question on SO here
See #SamQuayle's answer there with this link to the official docs.
As noted by various others Firebase does now support account related emails but even better, as of 10 days ago or so it also supports sending any kind of email via Firebase Functions. Lots of details in the docs and example code here.
I used following code to check the email verification after creating new account.
let firAuth = FIRAuth.auth()
firAuth?.addAuthStateDidChangeListener { auth, user in
if let loggedUser = user {
if loggedUser.emailVerified == false {
loggedUser.sendEmailVerificationWithCompletion({ (error) in
print("error:\(error)")
})
}
else {
print(loggedUser.email)
}
} else {
// No user is signed in.
print("No user is signed in.")
}
}
I used MandrillApp. You can create an API key that only allows sending of a template. This way even thought your key is exposed it can't really be abused unless someone wants to fire off tonnes of welcome emails for you.
That was a hack to get myself off the ground. I'm now enabling CORS from a EC2 that uses the token to verify that the user exists before extending them a welcome via SES.

Firebase authWithOAuthRedirect() woes

I'm trying to update my angularjs app to support Firebase 1.1 (I was stick with Firebase 1.0.x).
It deprecates firebasesimplelogin, including authentication inside Firebase core.
I have been able to successfully implement authentication using
authWithOAuthPopup("<provider>", function(error, authData) { ... });
It accepts a callback, which is passed authentication data in authData.
On the contrary, I can't undersand how to use
authWithOAuthRedirect("<provider>", function(error) { ... });
Firebase Authentication docs page is very concise... :-(. This is all what is said:
Alternatively [instead of authWithOAuthPopup], you may prompt the user to login with a full browser redirect, and Firebase will automatically restore the session when you return to the originating page
How do I get authData, when Firebase - after redirection - returns to my page?
The authData is available by registering a listener directly on the ref (so before calling authWithOAuthRedirect).
ref.onAuth(function(authData) {
...
}
ref.authWithOAuthRedirect("google", function(error) { ... });
See https://www.firebase.com/docs/web/guide/user-auth.html#section-monitoring-authentication
I think I'm running into the same issue as you. I'm trying to do Facebook authentication.
First, I'd like to clarify the reproduction steps for my issue.
My app is loaded on the client.
User clicks login with Facebook.
ref.authWithOAuthRedirect('facebook', ...) is called.
Client is redirected to Facebook and Facebook redirects client back to Firebase app
Despite successful authentication with Facebook, the callback passed to onAuth() is invoked (only once) with authData === null.
The callback passed to onAuth() is not invoked a second time with correct authData.
However, reloading the app causes the callback passed to onAuth to be invoked with correct authData. The reasons for this are not known to me but I suspect race condition.
Here's my workaround.
Before calling ref.authWithOAuthRedirect('facebook', ...) set yourself a flag in sessionStorage.
sessionStorage.reload = true;
ref.authWithOAuthRedirect('facebook', ...)
When the client is redirected to your app back from Facebook, you should be able to check for this flag and reload the page if necessary.
if (sessionStorage.reload) {
delete sessionStorage.reload;
setTimeout(function() {
location.reload();
}, 1000)
}
setTimeout(function() { ... }, 1000) helps fight the assumed race condition. I found 500 ms is insufficient time for the race condition to be resolved.
And one small gotcha: if you reload the page too soon, then authData remains null no matter how many times you reload the page.

Resources