Firebase: recent login requested - javascript

I'm dealing with Firebase authentication for web.
The documentation states that
Some security-sensitive actions—such as deleting an account, setting a primary email address, and changing a password—require that the user has recently signed in.
If not, the request would fail with error code auth/requires-recent-login and I should manage the case by prompting the user to re-insert her credentials. Once I have done that, I could easily re-authenticate the user with the following code:
firebase.auth().currentUser.reauthenticate(credential)
In the API reference there's some details more. It turns out credential is actually an object of type firebase.auth.AuthCredential. That being said, I still have a bunch of questions to which I couldn't find answer on the docs:
How do I create the AuthCredential object?
More importantly, how do I deal with providers (Google, Facebook, ...). I agree that changing email/password doesn't make sense for providers, because this is not the right place to change them, so re-authentication does not apply in this case. However, deleting a user is still an action requiring re-authentication, and this could be performed regardless of the authentication method. How do I re-authenticate a user that logged in with a provider?
The documentation states that the user must have logged in recently. I couldn't find any definition of recent in the docs.

You can initialize a credential by calling credential static method on any provider (include email/password provider):
firebase.auth.FacebookAuthProvider.credential(fbAccessToken);
To reauthenticate an OAuth provider, you can call in a browser signInWithPopup or redirect. This will return an object with 2 fields: user and credential. You can use that credential directly. Here is a simplified example:
var tempApp = firebase.initializeApp(originalConfig, 'temp');
var provider = new firebase.auth.FacebookAuthProvider();
tempApp.signInWithPopup(provider).then(function(result)) {
tempApp.auth().signOut();
originalApp.auth().currentUser.reauthenticate(credential);
});
That doesn't matter, as the firebase auth backend could change that. You shouldn't hard code this value. Instead try to catch that error and act appropriately when it happens.

You should reauthenticate with the provider;
import { getAuth, signInWithPopup, reauthenticateWithPopup, GoogleAuthProvider } from "firebase/auth";
const loginAuth = getAuth();
const googleProvider = new GoogleAuthProvider();
function reauthWithGoogle() {
return reauthenticateWithPopup(loginAuth, googleProvider)
}
and when you get the auth/requires-recent-login error call that function;
updatePassword(currentUser, "new password")
.catch(e => reauthWithGoogle()) //better check if the error is auth/requires-recent-login

Related

Link two federated auth providers to single email account?

In firebase, I'm using their authentication and I have a need to allow users to link multiple federated auth providers to their same firebase account. This seems to work well when the user has a email/password provider, and then I want to add either Google or Microsoft provider to their account.
However, I have use case where user is using Google for Authentication and now they want to add the Microsoft Provider. Instructions are here: https://firebase.google.com/docs/auth/web/account-linking#web-version-8_1 and it seems to indicate any other providers can be added to same account.
My snippet of code to link them to Microsoft as an example is:
const LinkNewProvider = async () => {
const provider = new firebase.auth.OAuthProvider('microsoft.com');
try{
const result = firebase.auth().currentUser.linkWithPopup(provider);
var credential = result.credential;
var user = result.user;
console.log(credential);
} catch(error) {
console.log(error)
}
}
When I try this, however, I get the error back "FirebaseError: Firebase: The email address is already in use by another account. (auth/email-already-in-use)."
What am I missing? Is it possible to link multiple federated providers to single account?

Why can't I use `allAuthenticatedUsers` for my Firebase Cloud Function?

When deploying Firebase Functions using the Firebase CLI, they are configured so that the Cloud Functions Invoker permission is granted to allUsers. With such a setting the code below functions as expected.
The Cloud Functions Invoker permission can also be granted to allAuthenticatedUsers. However, when I implement this change for addMessage, I only ever get a UNAUTHENTICATED error response using the code below.
Why won't allAuthenticatedUsers work for this Firebase Cloud Function?
Note: This Q&A is a result of a now-deleted question posted by Furkan Yurdakul, regarding why allAuthenticatedUsers wasn't working with his Firebase Callable Function for his Firebase app
MWE based on the documentation, with addMessage defined here:
firebase.auth().signInAnonymously() // for the sake of the MWE, this will normally be Facebook, Google, etc
.then((credential) => {
// logged in successfully, call my function
const addMessage = firebase.functions().httpsCallable('addMessage');
return addMessage({ text: messageText });
})
.then((result) => {
// Read result of the Cloud Function.
const sanitizedMessage = result.data.text;
alert('The sanitized message is: ' + sanitizedMessage);
})
.catch((error) => {
// something went wrong, keeping it simple for the MWE
const errorCode = error.code;
const errorMessage = error.message;
if (errorCode === 'auth/operation-not-allowed') {
alert('You must enable Anonymous auth in the Firebase Console.');
} else {
console.error(error);
}
});
Simply put, if the ID token passed to a Cloud Function represents a Google account (that used Google Sign-In through Firebase or Google itself), it works, otherwise, it doesn't.
Think of allAuthenticatedUsers as allAuthenticatedGoogleUsers instead of allAuthenticatedFirebaseUsers.
Background Information
For Callable Firebase Functions used with the Firebase Client SDKs, you will normally grant allUsers the permission to call it (the default setting Firebase CLI deployed functions).
A valid authenticated client request for a Google Cloud Functions must have an Authorization: Bearer ID_TOKEN header (preferred) or ?access_token=ID_TOKEN. Here, ID_TOKEN is a signed-in Google user's ID token as a JWT.
When Firebase Client SDKs call a Callable Function, they set the Authorization header for you with the current user's ID token (if the user is signed in, here). This is done so that the user's authentication token can be used in the context parameter of onCall() functions. Importantly though, a Firebase user's ID token doesn't always represent a Google user which makes it incompatible with allAuthenticatedUsers.
Because of this, you will have to gate your callable function in your code by checking context.auth and it's properties like below.
export const addMessage = functions.https.onCall((data, context) => {
if (!context.auth) {
// Throwing a HttpsError so that the client gets the error details.
throw new functions.https.HttpsError(
'failed-precondition',
'The function must be called while authenticated.'
);
}
// a valid user is logged in
// do work
});
Addendum on 403 Forbidden Errors
If your function is consistently throwing a 403 error after being deployed, this is likely because you are using an outdated copy of the Firebase CLI, as highlighted in the documentation:
Caution: New HTTP and HTTP callable functions deployed with any Firebase CLI lower than version 7.7.0 are private by default and throw HTTP 403 errors when invoked. Either explicitly make these functions public or update your Firebase CLI before you deploy any new functions.

How to sign user in after registration using AWS Amplify and React

When a user signs up using the withAuthenticator component, they are automatically logged in after confirming their email address. It this possible to automatically sign them in using a custom sign-in flow and the Auth object? How would this be accomplished?
I recently dealt with an issue in which I wanted to use the Storage module of Amplify, but had to implement the Auth module in order to do it. I had a working signin flow, and didn't want it disturbed, so I used the Auth api to sign users into AWS in the background, while signing them into my app using my original flow.
The docs describe how to sign a user in programmatically here: https://docs.amplify.aws/lib/auth/emailpassword/q/platform/js
Implementing the JS code isn't too hard
import { Auth } from 'aws-amplify';
async function signIn() {
try {
const user = await Auth.signIn(username, password);
} catch (error) {
console.log('error signing in', error);
}
}
I implemented a pre-sign up lambda trigger to auto confirm users, their email, etc bc I didn't want this Auth flow interrupting my existing flow. This method is described here:
https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-sign-up.html#aws-lambda-triggers-pre-registration-example-2
Here are the notes I took as I was working, in case it helps to give you more context. Sorry if they're confusing, they're only really meant for me to read: https://www.evernote.com/shard/s713/sh/ccf96dcc-b51e-9963-5207-ac410b02a13a/0bcdb4bc85ea8a31b7f5621a6812c837
As of Amplify JS API v4.3.29 it is now possible. Simply include the autoSignIn attribute in the signUp method.
Auth.signUp({
username: 'xxxxxx',
password: '*********,
attributes: {
email: 'xxxxxxxxxx'
},
autoSignIn: {
enabled: true
}
})

Why does Firebase signInWithPhoneNumber(number, recaptchaVerifier) create a NEW account if the number does not exist?

Action:
- signInWithPhoneNumber(NUMBER NOT IN DB, recaptchaVerifier)
Expected Behavior:
- Since number not in DB, it should not log me in.
Current Behavior:
- If the number does not exist in DB, it CREATES a new user after going through recaptcha + sms verification. WHY?
Code:
function loginWithSMS(phoneNumber) {
firebase.auth().useDeviceLanguage();
//#ts-ignore
window.recaptchaVerifier = new firebase.auth.RecaptchaVerifier("recaptcha-container");
//#ts-ignore
window.recaptchaVerifier.render().then(function (widgetId) {
//#ts-ignore
window.recaptchaWidgetId = widgetId;
});
// #ts-ignore
firebase
.signInWithPhoneNumber(phoneNumber, window.recaptchaVerifier)
.then((confirmationResult) => {
console.log("Login success", confirmationResult);
window.recaptchaVerifier.clear();
// SMS sent. Prompt user to type the code from the message, then sign the
// user in with confirmationResult.confirm(code).
const verificationCode = window.prompt(
"Please enter the verification " + "code that was sent to your mobile device."
);
return confirmationResult.confirm(verificationCode);
})
.catch((error) => {
console.error(error);
// Error; SMS not sent
// Handle Errors Here
window.recaptchaVerifier.clear();
return Promise.reject(error);
});
}
This is just how the API is defined: by sending a text to the number, Firebase allows the user to verify that they have access to that phone number. If they do, they're allowed to sign in.
This is the same for the email+password provider in Firebase Authentication. Calling firebase.auth().createUserWithEmailAndPassword(email, password) creates the user, even if they didn't exist yet. And while your code may not call this API, any developer can take the Firebase configuration data from your app and call the API themselves.
Most often when developers are asking about this they're confusing authentication with authorization.
When you authenticate, you are proving that you are you. So in the examples above, that you have access to a certain phone number, or that you know the email+password combination of the account.
Based on knowing who the user is, the application then authorizes that user to perform certain actions or to access certain data.
For example, if you're using Realtime Database, Cloud Storage, or Cloud Firestore, you can control access with Firebase's server-side security rules.
If you have a different back-end, you'd control it there by checking the information in the ID token of the user (which you get from Firebase Authentication) against some set of authorization rules for your application.
Also see:
Prevent user account creation with sign in by email in firestore (similar question, but then for passwordless email signin)
How to disable Signup in Firebase 3.x
How does the firebase authentication and realtime application database secure itself?

Can't destroy AWS Cognito session from within React application

I'm trying to log out of my application that's using AWS Cognito by calling their logout endpoint. I'm not using the AWS SDK because as far as I can tell, it does not yet cover oauth app integrations and sign in using external federated identity providers (please correct me if I'm wrong about that). I log in from an AWS-hosted login screen that I'm redirected to when I call their authorization endpoint. They redirect me back to my page with a "code" which I post back to them using their token endpoint to get tokens. All of this is textbook oauth 2.0 stuff.
The problem is that when I call the logout endpoint using a JavaScript browser redirect (window.location.href = ....) it doesn't clear the cookies that are set when I logged in ("XSRF-TOKEN" and "cognito") and I can't manually clear them because they were set from the AWS domain which is different from the one where my site is hosted. The cookies do get cleared when I enter the logout link in the address bar. There's clearly a difference between using window.location.href in code and dropping a link in my address bar.
To clear out the sessoin you need to use clearCachecId() and then reset the Cognito Id credentials. This is my function using the AWS SDK:
import AWS from 'aws-sdk/global';
const getCurrentUser = () => {
const userPool = newCognitoUserPool({
UserPoolId: YOUR_USER_POOL_ID,
ClientId: YOUR_APP_CLIENT_ID
});
return userPool.getCurrentUser();
}
const signOutUser = () => {
const currentUser = getCurrentUser();
if (currentUser !== null) {
curentUser.signOut();
}
if (AWS.config.credentials) {
AWS.config.credentials.clearCachedId(); // this is the clear session
AWS.config.credentials = new AWS.CognitoIdentityCredentials({}); // this is the new instance after the clear
}
}
That should take care of that.
It's a timing issue involving the use of windows.location and cookies. It seems that I was causing the same cookie, XSRF-TOKEN, to be unset and then reset so fast that it was just not happening at all. Inserting a timeout between logging out and redirecting back to the log in screen fixes the problem. There are some guys on this thread who seem to know something about it: https://bytes.com/topic/javascript/answers/90960-window-location-cookies

Categories

Resources