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

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.

Related

today is good day [duplicate]

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.

How to call permission dialog again when user clicked don't ask again

Hello guys i make a xamarin.forms app. I need to ask the user to allow app use his location. This is my code:
var location = await Geolocation.GetLastKnownLocationAsync();
if (location == null)
{
location = await Geolocation.GetLocationAsync(new GeolocationRequest
{
DesiredAccuracy = GeolocationAccuracy.Medium,
Timeout = TimeSpan.FromSeconds(30)
}); ;
}
if (location == null)
LabelLocation.Text = " NO GPS LOCATION";
else
LabelLocation.Text = $"{location.Latitude} {location.Longitude}";
}
catch (FeatureNotSupportedException fnsEx)
{
// Handle not supported on device exception
await Application.Current.MainPage.Navigation.PushAsync(new MainMenu());
}
catch (FeatureNotEnabledException fneEx)
{
// Handle not enabled on device exception
await Application.Current.MainPage.Navigation.PushAsync(new MainMenu());
}
catch (PermissionException pEx)
{
// Handle permission exception
await Application.Current.MainPage.Navigation.PushAsync(new MainMenu());
}
catch (Exception ex)
{
// Unable to get location
await Application.Current.MainPage.Navigation.PushAsync(new MainMenu());
}
when user click never show again , everytime open the page it goes on PermissionException which is ok. But i want to show again the permission dialog because without location can't show data. Is it possible to do this?
The short answer again is, no you can't. As you can see in the Requesting permissions documentation (when the user denies a permission and checks that no prompt should be shown from this moment forward):
Explain to the user that the feature is unavailable because the features requires a permission that the user has denied. At the same time, respect the user's decision. Don't link to system settings in an effort to convince the user to change their decision.
The best practice is to explain in detail to the user before you ask for the permission. If the user still decided to deny you this permission, then display a message describing that this particular logic simply won't work. It is against the best practices, but if you wish, you can redirect the user to Settings where the user can enable the permission. However, I strongly advise you not to do so.
One important side note, again from the documentation:
If the ContextCompat.checkSelfPermission() method returns PERMISSION_DENIED, call shouldShowRequestPermissionRationale(). If this method returns true, show an educational UI to the user. In this UI, describe why the feature, which the user wants to enable, needs a particular permission.
Also, use Xamarin.Essentials for permission checking. Everything is very well documented there.
P.S. I strongly suggest you read App permissions best practices where you will find other useful practices from the official Android team.

How to verify users current password?

So, maybe I missed this somewhere in the docs but I couldn't find anything of the sort.
I wan't my users to have to type in their current password to be able to create a new one. From what I understand if the user is authenticated he is able to update his password without providing his current one.
Even if this might be somewhat secure I would rather have him type his old one to prevent people from going on already authenticated sessions from say family members or so and changing the pw.
Is there any way to do this?
(I have no problem using the Admin SDK since I already set up a server for these kind of things)
UPDATE: (Use - reauthenticateWithCredential)
var user = firebaseApp.auth().currentUser;
var credential = firebase.auth.EmailAuthProvider.credential(
firebase.auth().currentUser.email,
providedPassword
);
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateWithCredential(credential).then(function() {
// User re-authenticated.
}).catch(function(error) {
// An error happened.
});
PREVIOUS VERSION
you can use reauthenticate API to do so. I am assuming you want to verify a current user's password before allowing the user to update it. So in web you do something like the following:
reauthenticateAndRetrieveDataWithCredential- DEPRECATED
firebase.auth().currentUser.reauthenticateAndRetrieveDataWithCredential(
firebase.auth.EmailAuthProvider.credential(
firebase.auth().currentUser.email,
providedPassword
)
);
If this succeeds, then you can call
firebase.auth().currentUser.updatePassword(newPassword);

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