Firebase authentication facebook provider usage with signInAndRetrieveDataWithCredential() - firebase

I'm trying to set up facebook sign-in to my react-native app using firebase. I am successfully authenticating with facebook using the integrated facebook support with expo and am getting a facebook-token. Unfortunately however, I'm having issues using this token to sign-in with firebase getting a "Invalid IdP response/credential" error.
I have used the exact same flow (but using Google Provider and token) for google login and been successful. I have also been able to use the facebook token to get data from facebook's rest API showing validity of token.
//This function gives the error
doSignInWithFacebookCredential = (facebookToken) => {
const credential = this.facebookProvider.credential(facebookToken);
this.auth.signInAndRetrieveDataWithCredential(credential)
.then(authUser => {
alert("It worked!")
})
.catch(function(error) {
alert(error)
});
}
//Code used to verify facebook token
const response = await fetch(`https://graph.facebook.com/me?access_token=${facebookToken}`);
console.log(await response.json())
//This function works perfectly (The google sign-in with same flow)
doSignInWithGoogleCredential = (googleToken) => {
const credential = this.googleProvider.credential(googleToken);
console.log(credential)
this.auth.signInAndRetrieveDataWithCredential(credential)
.then(authUser => {
alert("It worked!")
})
.catch(function(error) {
alert(error)
});
}
After receiving the valid facebook token an error occurs resulting in an alert from the catch that says
Error: Invalid IdP response/credential: http://localhost?id_token=$facebookToken&providerId=facebook.com
TLDR; Despite having a valid facebook token it seems facebookProvider.credential(facebookToken) is not giving a valid token to the signInAndRetrieveDataWithCredential() function causing a 'Invalid IdP response/credential' error.
If anyone has worked with these methods before or has any suggestions they are all greatly appreciated.

Thrown if the credential is malformed or has expired, and for that reason it returns invalid idp response credentials.
Since This method is deprecated, i suggest to use firebase.auth.Auth.signInWithCredential instead. And, maybe in this way, you will get more details if there is a problem with credential or not
Hope this helps!

Related

How do I securely get an Id_Token for a user securely through Google Credentials? I'm worried a user could change the URL (Using Expo Go/React Native)

Hello! I am OCD about security for my app and was wondering how to properly login/signup a user with the Google auth provider.
I have a client ID and secret for that client ID - from Google Credentials - for my app. I know not to put the secret in the client.
The code below works perfectly but I'm unsure if it's safe to generate an id_token for a user directly without any server code because of this doc from Expo Go:
Notice it says "be sure that you don't directly request the access token for the user". I don't know what this means exactly.
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId:
"my-client-id-goes-here.google.apps.com",
});
React.useEffect(() => {
if (response?.type === "success") {
const { id_token } = response.params;
const credential = new GoogleFirebase.GoogleAuthProvider.credential(
id_token
);
}
}, [response]);
Any ideas on how to execute this securely to make sure a user can't change the URL redirect parameters from Google or anything? I'm just not 100% on what I'm doing here.
Use an .env file to store your sensitive information. For example in your .env file put
CLIENTID="my-client-id-goes-here.google.apps.com"
Then call it like you did above:
const [request, response, promptAsync] = Google.useIdTokenAuthRequest({
clientId:
process.env.CLIENTID,
});

Firebase user.delete() method works but with error

I am using angular, and have an application that stores user details, and login info. When trying to delete a user, I am first deleting all the user related information. Then asking the user to re-authenticate themselves, after authentication, user gets logged out, and their basic details fetched to show profile id deleted followed by their sign-in info using user.delete().
All this works as expected, but at the end I am getting an error. Why am I am getting this error even when I have already logged out the user of the application.
Error Message: {code: "auth/user-token-expired", message: "The user's credential is no longer valid. The user must sign in again.", a: null}
My code -
deleteAccount(){
var userToDelete = firebase.auth().currentUser;
this.logout();
this.store.dispatch(UI.StartAppLoad({status:'Deleting User Details...'}));
this.userService.DeleteUser(userToDelete.uid)
.then((res)=>{
console.log(res);
}).catch(this.HandleError.bind(this));
userToDelete.delete().then(
(res)=>{
console.log(res);
this.uiService.showSnackbar('User Account Deleted',null,3000);
this.store.dispatch(UI.LoadApp());
}
).catch(this.HandleError.bind(this));
}
logout() {
this.afAuth.signOut();
}
where, HandleError is used to display the Error Message in a snackbar.
deleteAccount() is called after the user successfully authenticates themselves.
Instead of getting the error message displayed, I want to display the message 'User Account Deleted'.
Entire Flow -
onDeleteAccount(){
const confirmResult = this.uiService.showConfirm({
isDanger:true,
title:'Delete Account?',
content:'All your user account data will be permamnently deleted.'+
' You will need to create a new account later. Are you sure you want to continue?',
okText:'Delete'
});
confirmResult.subscribe(async isDelete=>{
if(isDelete){
this.store.dispatch(UI.StartAppLoad({status:'Deleting Excercise Data...'}));
const isResetDone = await this.trainingService.resetPastExercise();
if(isResetDone){
this.store.dispatch(UI.StartAppLoad({status:'Deleting Follow list...'}));
this.userService.clearFollowList();
this.authService.actionToPerform.next(actions.Delete_Account);
this.store.dispatch(UI.LoadApp());
this.router.navigate([AppRoutes.ReAuthenticate]);
}
}
});
}
Authenticate Page's submit() method:
this.authService.reauthenticate({
email:form.value.email,
password:form.value.password
});
this.authService.deleteAccount();
AuthService:
reauthenticate(authdata: AuthData) {
this.store.dispatch(UI.StartLoading());
var credential = firebase.auth.EmailAuthProvider.credential(
authdata.email,
authdata.password
);
this.afAuth.currentUser.then((user) => {
user.reauthenticateWithCredential(credential)
.then((res)=>{
this.prevPwd = authdata.password;
console.log(res);
this.store.dispatch(UI.StopLoading());
})
.catch(this.HandleError.bind(this))
});
}
And then the above method deleteAccount()
Please suggest.
As explained in the doc, this happens because:
delete() is a security-sensitive operation that requires the user to
have recently signed in.
The doc also indicates that:
If this requirement isn't met, ask the user
to authenticate again and then call firebase.User.reauthenticateWithCredential.
So, you need to handle this specific error and do as indicated by the doc, i.e. "ask the user to authenticate again and then call reauthenticateWithCredential."

Firebase service account to generate authentication token for client-side use with Google Apps Script

I am having difficulty using the FirebaseApp (a 3rd party API) to generate an authentication token that can be passed to a sidebar and used by the client to login and access my Firebase Database client-side.
I'm trying to use this tutorial but cannot get it working without using a database secret (which is being depreciated) in makeToken(). I'd prefer to use a service account as reflected in this tutorial. When I look at the difference between the tokens generated, the first 2 pieces separated by a '.' are identical, the last piece after the final '.' is different. The lengths are the same as well. eg:
//Example Generated by Database Secret: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.dGhlIG1pbmQsIHRoYXQgYnkgYSBwZXJzZXZlcmFuY2U=
//Example Generated by Service Account: TWFuIGlzIGRpc3Rpbmd1aXNoZWQsIG5vdCBv.ZGdlLCBleGNlZWRzIHRoZSBzaG9ydCB2ZWhlbWVuY2Ugb2YgYW55IGNhcm5hbCBwbGVhc3VyZS4=.IHNpbmd1bGFyIHBhc3Npb24gZnJvbSBvdGhlciBhbml=
I can generate the OAuth access token, pass it to FirebaseApp and generate an authentication token, but when it is passed client-side and I attempt to authenticate I get an error: Login Failed! Error: INVALID_TOKEN: Failed to validate MAC.
It seems like there is a lot of misinformation and conflicting information on how this should be done.
I have a getFirebaseService() function server-side that uses Apps Script OAuth2 Library to get an access token.
function getFirebaseService() {
return OAuth2.createService('Firebase')
// Set the endpoint URL.
.setTokenUrl('https://accounts.google.com/o/oauth2/token')
// Set the private key and issuer.
.setPrivateKey(fb_PRIVATE_KEY) //Service account private key
.setIssuer(fb_SERVICE_EMAIL) //Service account email
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getScriptProperties())
// Set the scopes.
.setScope('https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/firebase.database');
}
I have a makeToken() function server-side that gets an authentication token from Firebase using the OAuth access token. I am able to use the service.getAccessToken() OAuth token server-side to access and store data. So that works, I guess my issue is creating a client auth token that's more restrictive.
function makeToken(){
var service = getFirebaseService();
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //Database Secret Works: "AAslhfi3MYACCESSTOKEN2930hf03ah4th8" but is being depreciated.
.createAuthToken(Session.getActiveUser().getEmail());
} else {
Logger.log("makeToken: " + service.getLastError());
}
}
Then client-side, from the sidebar, I try to authenticate with a custom auth token retrieved server-side from makeToken().
var userAuthToken;
google.script.run.withSuccessHandler(function (requestAuthToken) {
userAuthToken = authenticateClient(requestAuthToken)
}).makeToken();
function authenticateClient(userRequestToken) {
var ref = new Firebase(fb_URL);
ref.authWithCustomToken(userRequestToken, function (error, authData) {
if (error) {
console.log("FB Login Failed!", error); //Error below come from here.
}
else {
console.log("FB Login Succeeded!", authData);
}
});
return ref.authData.auth;
}
This results in Login Failed! Error: INVALID_TOKEN: Failed to validate MAC..
Edit: Is it possible FirebaseApp is incorrectly generating the JWT Authentication Token?
Edit2: I think the above edit is unlikely as I attempted to use the GSApp library and had the same issue. It only seems to want the depreciated database secret, not a service account OAuth.
Alright, so after a very long day I figured it out. I'm going to lay out what I ended up using for libraries and what the issue was (see the third library). The main problem was essentially that the tutorial was outdated and no a lot of people use Firebase in apps script.
OAuth2 (Server-side)
Link
I didn't have to change anything here! It was working fine and never an issue.
FirebaseApp (Server-side)
Link
This is a nice library and I stuck with it because it worked well (once I got it there). I had to make a change to my original code that came from the tutorial I mentioned. My code ended up like this and worked:
if (service.hasAccess()) {
return FirebaseApp.getDatabaseByUrl(fb_URL, service.getAccessToken()) //get OAuth Token
.createAuthToken(Session.getEffectiveUser().getEmail(), null, serviceAccount.client_email, serviceAccount.private_key);
//... Added the null, private key, and service email parameters.
Firebase (Client-side)
Link
Alright, so this is where my main issue was -- The tutorial I followed for client-side setup was old. I had to upgrade the code on my own to use the new 3.x version:
<script src="https://www.gstatic.com/firebasejs/5.8.2/firebase.js"></script>
// Initialize Firebase
var config = {
apiKey: "<Web API Key>",
authDomain: "<Project ID>.firebaseapp.com",
databaseURL: "https://<DB URL>.firebaseio.com/"
};
firebase.initializeApp(config);
With this firebase instance I was able to update my original authenticateClient() method:
function authenticateClient(userRequestToken) {
firebase.auth().signInWithCustomToken(userRequestToken).catch(function(error) {
// Handle Errors here.
console.error("authClient: ", error.code, error.message);
});
return {
uid: firebase.auth().currentUser.uid,
metadata: {
lastSignInTime: firebase.auth().currentUser.lastSignInTime
}
};
}
That's it! I now have a firebase instance with a signed in user via JWT Custom Token! I came across a few people with similar issues an I hope this helps.

Cannot pass a read permission (email) to a request for publish authorization

I am trying to use Facebook login using Firebase authentication. I have followed whole documentation. Lastly whenever I click the login button it gives an error saying:
Cannot pass a read permission (email) to a request for publish authorization
The line on which it is showing error is:
LoginManager.getInstance().logInWithPublishPermissions(LoginActivity.this,Arrays.asList("email", "public_profile"));
Can someone explain me what I am doing wrong? I have looked other answers also but no help from there.
Use logInWithReadPermissions instead of logInWithPublishPermissions. The error message is very clear about that, you are trying to request read permissions with a function that is being used for publish permissions.
.getInstance().logInWithPublishPermissions is not Firebase Authentication. Signin via federated providers such as Facebook is available via the Firebase JavaScript (web) SDK. See Authenticate Using Facebook Login with JavaScript for details.
firebase.auth().signInWithPopup(provider).then(function(result) {
// This gives you a Facebook Access Token. You can use it to access the Facebook API.
var token = result.credential.accessToken;
// The signed-in user info.
var user = result.user;
// ...
}).catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
// The email of the user's account used.
var email = error.email;
// The firebase.auth.AuthCredential type that was used.
var credential = error.credential;
// ...
});

Firebase Web Admin

First of all, I am using nodejs for the backend. I use firebase hosting and firebase functions to deploy an express() app.
What I am trying to achieve is to make an admin website, which is connected to Firebase. so I have a route /admin/ like this:
adminApp.get("/", (request, response) => {
return response.redirect("/admin/login");
});
Here I basically want to check if a current user is logged in - or not.
I know firebase supports client side authentication using:
firebase.auth().onAuthStateChanged(user => {
if (user) {
} else {
}
});
And using
function login() {
var userEmail = document.getElementById("email").value;
var userPass = document.getElementById("password").value;
firebase.auth().signInWithEmailAndPassword(userEmail, userPass).catch(function(error) {
var errorCode = error.code;
var errorMessage = error.message;
if (error) {
document.getElementById('loginError').innerHTML = `Error signing in to firebase`;
}
});
}
However image this case:
Someone (not an admin) is visiting /admin/some_secret_website/ which he obviously does not have access to.
If I rely on client side authentication, it first loads the entire website and the scripts and then notices - hey I am not authenticated, let me redirect to /login. By then however anyone knows the source code of an admin page.
I'd rather have something like:
adminApp.get("/admin/some_secret_website", (request, response) => {
if (request.user) {
// user is authenticated we can check if the user is an admin and give access to the admin page
}
});
I know that you can get the user's token and validate that token using the AdminSDK, but the token must be send by the client code, meaning the website was already loaded.
I came across Authorized HTTPS Endpoint by firebase, but it only allows a middleware when using a bearer token.
Does anybody know how I can maintain a server side user object to not even return admin html to the browser but only allow access to admins?
Like Doug indicated, the way your admin website/webapp would function with Firebase Cloud Functions (which is effectively a Nodejs server) is that you get the request, then use the headers token to authenticate them against Firebase Auth. See this answer for a code snippet on this.
In your case, I'm thinking you would create a custom claim for an "administrator" group and use that to determine whether to send a pug templated page as a response upon authentication. As far as Authorization, your db rules will determine what said user can CRUD.

Resources