AWS cognito signout from google account not working properly - javascript

I'm facing a problem with aws cognito login. When I'm logging in with google, it is fine. but when I'm logging out and again clicking on "sign in with google" button it is taking me to the previous account that I logged in before logging out without asking me to select an account. But when I clear the cookies clicking on the "View site information" button on the address bar(Cannot find anything in Application=>cookies storage, that's why I had to use this method) and log out, and again try to login with google then it is asking me to select a google account to log in. But without clearing cookies from view site information it is taking me to previous account without giving me list of account to log in.
See this image that may help you to understand better:
Here is my logOut function:
const logout = () => {
window.location.reload();
const user = Pool.getCurrentUser();
if (user) {
user.signOut();
}
const accessToken = JSON.parse(localStorage.getItem("accessToken"));
const refreshToken = JSON.parse(localStorage.getItem("refreshToken"));
if (accessToken) {
localStorage.removeItem("accessToken");
}
if (refreshToken) {
localStorage.removeItem("refreshToken");
}
const authUser = JSON.parse(localStorage.getItem("authUser"));
if (authUser) {
localStorage.removeItem("authUser");
}
navigate(RoutingPaths.Login);
};
authenticate function:
const authenticate = (Username, Password) => {
return new Promise((resolve, reject) => {
const user = new CognitoUser({
Username,
Pool: Pool,
});
const authDetails = new AuthenticationDetails({
Username,
Password,
});
user.authenticateUser(authDetails, {
onSuccess: (data) => {
resolve(data);
},
onFailure: (err) => {
reject(err);
},
newPasswordRequired: (data) => {
resolve(data);
},
});
});
};

I don't think it's anything wrong that you do with the logout on your part. Most probably it's Amazon Cognito remembering the preferred user and trying to log in with that user. If your Google session for that user was expired, I'm pretty sure that you would have seen that "choose account" screen again.
When you clear cookies through that "i" icon in the browser, you also clear Cognito's cookies. That's why it forgets the preferred Google user and asks to choose the account again. You don't see those cookies in the Application -> cookie storage, because the browser only shows localhost cookies there.

Related

Email provider: How to preserve value in `signIn` callback between sending email and verification calls?

I want to implement role based authentication in my app.
After researching for a while I found a simple solution - to pass role to signIn function
signIn('email', { email, role: 'user' });
Then I can unpack it from req's body
import NextAuth from 'next-auth'
export default async function auth(req, res) {
return NextAuth(req, res, {
// providers, adapters, etc.
callbacks: {
signIn: async ({ user }) => {
const { body: { role } } = req;
if (role) {
user.role = role;
}
return true;
},
session: async ({ user, session }) => {
session.user = {...session.user, ...user };
return session
}
}
}
});
Or so I thought. Magic link sign in flow has two steps: sending email and confirming it.
The role I provide to signIn function is only available during the first step and saving user to users collection only happens after user confirms email, so when user confirms email there's no role.
I tried storing role in cookies
if (role) res.setHeader('Set-Cookie', 'user-role=' + role + ';Max-Age=600');
But cookies get overriden after I confirm email and the role is lost.
I don't want to create additional collections and/or records in my database.
How can I preserve role inside signIn callback without storing it in a separate database collection? Maybe there's some other solution you can think of?

Firebase: Re-authenticate the current user inside a Cloud Function

I am implementing a cloud function for updating the current user's password.
Basically, the logic I want to follow is:
(Client side)
0. Complete form and submit the data (current password and new password).
(Backend)
1. Get the current user email from the callable function context.
2. Re-authenticate the current user using the provided current password.
2.1. If success, change the password and send a notification email.
2.2. Else, throw an error.
Here is my current code:
const { auth, functions } = require("../../services/firebase");
...
exports.updatePassword = functions
.region("us-central1")
.runWith({ memory: "1GB", timeoutSeconds: 120 })
.https.onCall(async (data, context) => {
const { currentPassowrd, newPassword } = data;
const { email, uid: userId } = context.auth.token;
if (!userId) {
// throw ...
}
try {
//
// Problem: `firebase-admin` authentication doesn't include
// the `signInWithEmailAndPassword()` method...
//
await auth.signInWithEmailAndPassword(email, currentPassowrd);
await auth.updateUser(userId, {
password: newPassword,
});
sendPasswordUpdateEmail(email);
} catch (err) {
// ...
throw AuthErrors.cannotUpdatePassword();
}
});
My problem is that the firebase-admin package doesn't include the signInWithEmailAndPassword, and I need a way to handle this, to check that "currentPassword" is correct, inside my function.
My other option, if the one I have described is not possible, is to update the password using the firebase sdk in the client side, and then to call a firebase function to send the notification email.
Strictly speaking you don't need to re-authenticate the user in the Cloud Function: If you get a value for context.auth.uid in your Callable Cloud Function, it means that the user is authenticated in the front-end and you can therefore safely call the updateUser() method.
If you want to deal with the case when the user left his device opened, and someone updates his password, as explained in the comments under your question, I would suggest you use the reauthenticateWithCredential() method in the front-end, which re-authenticates a user using a fresh credential.
Do as follows:
import {
EmailAuthProvider,
getAuth,
reauthenticateWithCredential,
} from 'firebase/auth'
const email = auth.currentUser.email;
// Capture the password value
// e.g. via a pop-up window
const password = ...;
const auth = getAuth();
const credential = EmailAuthProvider.credential(
email,
password
);
await reauthenticateWithCredential(
auth.currentUser,
credential
);
// If no error is thrown, you can call the Callable Cloud Function, knowing the user has just re-signed-in.

After signing out, my firebase idToken is still valid?

I have the following code in my firebase front-end:
document.querySelector('#sign-out').addEventListener('click', () => {
firebase.auth().signOut().then(() => {
document.location = '/sign-in';
}, (error) => {
console.error('Sign Out Error', error);
});
})
This seems to be working. I see the network request succeed, and the redirect to /sign-in occurs.
However, when I then navigate to /chatroom, I am considered logged in. My python/flask backend has the following code on the endpoint:
#app.route("/chatroom")
def chatroom():
if 'idToken' in request.cookies:
id_token = request.cookies.get("idToken")
try:
decoded_token = auth.verify_id_token(id_token)
except:
print("INVALID TOKEN")
return redirect(url_for("sign_in"))
return render_template("chatroom.html")
else:
return redirect(url_for("sign_in"))
Rather than redirecting the user to /sign-in when I hit this path, Firebase is verifying that the token is valid and so I am allowed to proceed, even though I had logged out.
What am I missing?
Edit:
Changed code to this for debugging purposes:
firebase.auth().signOut().then(() => {
firebase.auth().onAuthStateChanged((user) => {
if (user) {
console.log('still signed in');
} else {
console.log('signed out');
}
});
}, (error) => {
console.error('Sign Out Error', error);
});
It outputs signed out.....so I'm quite confused.
Signing out does not invalidate the token. It just causes the SDK to forget about it, so it doesn't get refreshed or passed along to other Firebase products automatically. The old token will be valid for up to 1 hour, until it needs to be refreshed. If not refreshed, then it will fail verification.
If you are saving the ID token in a cookie, you should also remove it from the cookie in order to effectively sign out, so it doesn't get passed along to your backend.

Firebase Anonymous User once upgraded

I'm implementing a workflow where every user using my app is an anonymous user until they sign-in/up (either email or Google).
To sign up, it's straightforward: I use linkWithPopup.
However, I had some issues with user signing in: I try to link them and, if I get the auth/credential-already-in-use error (happens if the user upgrades once, signs out and then try to sign in again), I sign them in.
firebase.auth().currentUser.linkWithPopup(provider).then(function (result) {
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/credential-already-in-use') {
return firebase.auth().signInWithCredential(error.credential);
}
}).then((result) => {
return dispatch(loadContent(result.user.uid))
}).then(() => {
history.push('/');
});
The code above is working great and that's less hassle than doing it all by myself.
However, how do I remove the anonymous users which are created and orphan in case the user signs in?
I tried to make a reference to the old useless anonymous user and to delete it once the user is signed in (and so, changed its account) but it is obviously not working because the account changed and that would be a big security flaw if a user could delete another one...
I'm not very familiar with Firebase ecosystem, how should I handle that? Do I need to use a combination of Firebase Cloud Function and Firebase Admin SDK or is there a standard way of solving this problem?
It is quite simple. APIs on the user continue to work even if the user is not current.
const anonymousUser = firebase.auth().currentUser;
firebase.auth().currentUser.linkWithPopup(provider)
.then(function (result) {
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function (error) {
// Handle Errors here.
var errorCode = error.code;
if (errorCode === 'auth/credential-already-in-use') {
return firebase.auth().signInWithCredential(error.credential);
}
}).then((result) => {
if (anonymousUser.uid !== result.user.uid) {
return anonymousUser.delete()
.then(() => {
return result;
});
}
return result;
}).then((result) => {
return dispatch(loadContent(result.user.uid))
}).then(() => {
history.push('/');
});

Firebase - Change displayName for Facebook provider

I'm fairly new to the Firebase ecosystem, so I hope I'm not asking something too basic.
I'm using the firebase-js-sdk along with an e-mail + password registration. When the user signs up using an e-mail I prompt them to select their username and I store it using the user.updateProfile() method. This works fine, as the next time I call firebase.auth().currentUser I see the displayName property containing the updated value.
As for facebook, I'm using the react-native-fbsdk, and I authenticate the user using the following function:
const fbLogin = () => {
return new Promise((resolve, reject) => {
LoginManager
.logInWithReadPermissions(['public_profile', 'email'])
.then((result) => {
if (result.isCancelled) {
console.log('login cancelled');
} else {
AccessToken
.getCurrentAccessToken()
.then((data) => {
const credentials = firebase.auth.FacebookAuthProvider.credential(data.accessToken);
firebase
.auth().signInWithCredential(credentials)
.then((res) => resolve(res))
.catch((err) => reject(err));
})
}
}).catch(err => reject(err));
});
}
Once I store the user's data on Firebase I ask him to choose an username and I update the displayName following the same steps of the e-mail authentication. This seems to work too because if I call firebase.auth().currentUser I see the updated displayName. The only problem is when I reload the app the displayName is back to the facebook name.
My questions are:
Is it possible to override the displayName provided by Facebook?
If so, is this the correct approach to do so?
Thanks in advance to anyone that will help :)

Categories