Firebase custom claims is not actually setting for me - Python backend - javascript

Right now this is the code I am using to set the custom claim called "moderator":
import firebase_admin
from firebase_admin import credentials
from firebase_admin import auth
cred = credentials.Certificate(r".\sa.json")
firebase_admin.initialize_app(cred)
EMAIL = "bearcodes#outlook.com"
user = auth.get_user_by_email(EMAIL)
uid = "H9c1PQtSHxZfV3AWW5pQjNlSDnX2" # This is for bearcodes#outlook.com (this is of course an example)
auth.set_custom_user_claims(uid, {'moderator': True})
When I run it, there are no errors and it says nothing.
My cloud firestore rules are as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /pupils/{document=**} {
allow read: if request.auth.uid != null;
}
match /cclaims/{claims} {
allow read: if request.auth.uid != null;
allow write: if request.auth.token.moderator == true;
}
}
}
However when I run this simulated test (via the console) while logged into the website, it produces the error: Error writing document: FirebaseError: Missing or insufficient permissions.
This is the "simulated test" that I ran:
function testccl() {
db.collection("cities").doc("LA").set({
name: "Los Angeles",
state: "CA",
country: "USA"
})
.then(() => {
console.log("Document successfully written!");
})
.catch((error) => {
console.error("Error writing document: ", error);
});
}
Thanks in advance and I look forward to hearing your answer(s).
-The SimsRedux Team

In your test function, write to the claims collection, you may need to reload and get a new token with the moderator claim as well.

Related

How to fix firebase "User is not authorized" error even though user is authenticated in registration flow?

Im trying to do a variety of firebase actions in one call in a react-native app using react-native-firebase. the flow goes something like this:
create user in authentication
send image to storage
send data to firestore
During the image-storage phase, the imgRef.putFile() function errors out saying the user isn't authorized. However im using createUserWithEmailAndPassword() (which authenticates a user in on completion) and then using the returned credential to do the rest of the work, image storage and firestore creations.
firebase storage rules are set to allow only authenticated users. here's the ruleset:
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
}
}
Also, I have enabled anonymous signin in authentication methods.
here's the initial action up until the error point:
return (dispatch) => {
dispatch({ type: types.REGISTER_USER });
console.log('starting registration process...');
firebase
.firestore()
.collection('users')
.where('username', '==', username)
.get()
.then((querySnapshot) => {
console.log(querySnapshot);
if (querySnapshot.empty !== true) {
registrationFail(dispatch, 'Username already taken. Try again.');
console.log("Registrant's username already exists");
} else {
console.log('Registrants username is unique');
firebase
.auth()
.createUserWithEmailAndPassword(email, pass)
.then((userCredential) => {
uploadImg(dispatch, img, userCredential.user.uid)
here's the uploadImg() function:
const uploadImg = async (dispatch, uri, uid) => {
console.log('Starting image upload...');
dispatch({ type: types.UPLOAD_IMG, info: 'Uploading profile image...' });
const uploadUri = Platform.OS === 'ios' ? uri.replace('file://', '') : uri;
const imgRef = firebase.storage().ref('profile-images').child(uid);
return imgRef
.putFile(uploadUri, { contentType: 'image/png' })
.then((success) => {
console.log('profile image upload successful.')
return imgRef;
})
.catch((err) => {
console.log('profile image upload failed: ' + err)
uploadImgFail(dispatch, err.message);
});
};
again, the error that logs in the console is:
profile image upload failed: Error: User is not authorized to perform the desired action.
logging the firebase.auth().currentUser right before the uploading() function returns the current user object successfully.
this security rule issue also happens with Firestore, despite my security ruleset for the given collection being:
match /databases/{database}/documents {
match /users/{uid} {
// allow new user to check phone numbers
allow read: if true
allow update, create: if request.auth != null
allow delete: if request.auth.uid == uid
}
}
This is a part of my registration flow. I collect input, send relevant data to redux action, create a user, once the user is created, I add some data to a document in firestore. this makes no sense. im referencing the documentation and it still doesn't work.
How can this be fixed?
It seems to be an issue with firebase that you have to logout() the user once before you can log in after user creation. I faced the same issue and this is my workaround:
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not log in user');*/
console.log(error);
firebase.auth().createUserWithEmailAndPassword(userEmail,userPass).catch((error) => {
/*console.log('Could not create user');*/
console.log(error);
}).then(result => {
firebase.auth().signOut().then(() => {
firebase.auth().signInWithEmailAndPassword(userEmail,userPass).then(result => {
/*console.log("here is you logged in user");*/
})
});
});
});
});

Firebase Firestore Permission Denied Error when signInWithEmailAndPassword

I want to create a node js app which downloads some data from the firestore. Although I've done everything like it's shown in tutorials I've been stuck with reading document from the firestore for hours. I have a very simple database structure with simple security rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /partners/{document=**} {
allow read: if true;
}
}
}
In my code I just want to login into firebase using email and password and then download one document under existing path:
const firebase = require("firebase/app");
require("firebase/auth");
require("firebase/firestore");
const email = <correct email>
const password = <correct password>
var firestoreConfig = {
...
};
// Initialize Firebase
firebase.initializeApp(firestoreConfig);
firebase.auth().signInWithEmailAndPassword(email, password)
.then(function(user){
const userId = user.user.uid
const firestore = firebase.firestore();
console.log(`logged to firestore as ${userId}`)
firestore.doc("/partner/test/communication/544a3deec/messages/2e5b89b8-c48f-4d4f").get()
.then(function(data){
console.log(`${Object.keys(data).length}`);
})
.catch(function(error){
console.log(error);
})
})
and the error is
{ FirebaseError: Missing or insufficient permissions.
at new FirestoreError (/Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:1205:28)
at fromRpcStatus (/Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:5246:12)
at fromWatchChange (/Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:5482:35)
at PersistentListenStream.onMessage (/Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:15817:27)
at /Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:15750:30
at /Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:15786:28
at /Users/cb/Documents/IdeaProjects/node-hello/node_modules/#firebase/firestore/dist/index.node.cjs.js:14218:20
at process._tickCallback (internal/process/next_tick.js:68:7)
code: 'permission-denied',
name: 'FirebaseError',
toString: [Function] }
I see that the login was successful because it printed out the uid of the user. What can be the issue ? Security rules or I just completely don't understand the firestore ?
EDIT:
Changed my code according to Doug answer:
firebase.initializeApp(firestoreConfig);
firebase.auth().onAuthStateChanged(function(user) {
if (user) {
const userId = user.uid
const firestore = firebase.firestore();
console.log(`logged firestore as ${userId}`)
firestore.doc("/partner/test/communication/544a3deec/messages/2e5b89b8-c48f-4d4f").get()
.then(function(data){
console.log(`${Object.keys(data).length}`);
})
.catch(function(error){
console.log(error);
})
} else {
}
});
firebase.auth().signInWithEmailAndPassword(email, password)
.catch(function(error){
console.log(error);
})
same error as before
There is a typo in the code, an additional s for partner.

firestore rules not working even though test said they did

problem:
when i use the firestore request emulator, it works how i want but when i test it through my app, it lets me make create requests even though i am not authenticated.
note i am using react, easy peasy state managment & firebase spark plan.
code:
rules
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow write: if request.auth != null;
allow read;
}
}
}
submitting to database:
componentDidMount() {
try {
this._db = firebase.firestore();
this._db
.collection("txts")
.limit(50)
.orderBy("timeMade", "asc")
.onSnapshot((querySnapshot) => {
this.setState({ messages: [] });
querySnapshot.forEach((doc) => {
this.setState({
messages: [...this.state.messages, doc.data().txt],
});
});
});
} catch (err) {
alert("Must sign in first!");
}
}
uploadData = (e) => {
e.preventDefault();
if (this.state.m.trim()) {
this._db.collection("txts").add({
txt: this.state.m,
timeMade: Date.now(),
});
this.setState({ m: "" });
}
};
things i have tried:
i have waited 1 minute (as it said to wait up to 1 minute)
copied pasted rules from medium post that did what i wanted (still didn't work)
The allow read; without any condition in your rules is equivalent to allow read: if true;, so it allows anyone to read all data.
If you want to prevent anyone from reading the data, that'd be:
allow read: if false;
If you only want authenticated users to reading data, that'd be:
allow read: if request.auth != null;

Updating private field with Firebase Cloud: best practice for security

I'm using Firestore with some private documents (no write). My rules are already setup for this. For example, a document could contain the credits or subscription tier for a user. I want to let the backend update these fields instead of the client, for obvious reasons. However, I was wondering, if I create a generic updatePrivateField method in Cloud functions, would it be considered best practice?
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
if (!context.auth) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, id, update } = data;
try {
await admin
.firestore()
.collection(collection)
.doc(id)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
});
Basically, what I am wondering is, is creating an endpoint like this considered safe? I am checking if the user is authenticated, but couldn't they just POST to the endpoint with their own login credentials and update any field in the database?
Thanks, I appreciate any help!
In case of a user can update own document.
Should set context.auth.uid as document id.
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
// Check context.auth.uid
if (!context.auth || !context.auth.uid) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, update } = data;
// Set context.auth.uid as document id
try {
await admin
.firestore()
.collection(collection)
.doc(context.auth.uid)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
});
In case of a some role (ex. admin) can update a user document.
Should use Custom Claims and check it.
See https://firebase.google.com/docs/auth/admin/custom-claims
ex. Use a admin role
// Add any trigger or any condition you want.
// Set admin privilege on the user corresponding to uid.
admin.auth().setCustomUserClaims(uid, {admin: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
exports.updateProtectedField = functions.https.onCall(async (data, context) => {
// Check user is admin
if (!context.auth || !context.auth.token.admin) {
throw new functions.https.HttpsError(
"failed-precondition",
"Authentication Required"
);
}
const { collection, id, update } = data;
try {
await admin
.firestore()
.collection(collection)
.doc(id)
.update({
...update,
});
return { msg: "Update successful", code: 200 };
} catch (error) {
throw new functions.https.HttpsError("unknown", error.message, error);
}
More documents
https://firebase.google.com/docs/reference/functions/providers_https_.callablecontext#auth
https://firebase.google.com/docs/reference/admin/node/admin.auth.DecodedIdToken
https://cloud.google.com/functions/docs/concepts/functions-and-firebase
https://firebase.google.com/docs/auth/admin/verify-id-tokens#verify_id_tokens_using_the_firebase_admin_sdk
https://cloud.google.com/endpoints/docs/openapi/authenticating-users-firebase
Your Cloud Functions code allows any authenticated user to update any document. It is pretty much the equivalent of these security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{collection/{document=**} {
allow write: if request.auth != null;
}
}
}
If that is what you want to accomplish, I recommend doing so with the above security rules as it'll be both simpler and cheaper than introducing Cloud Functions to the mix.
If a user should only be able to update their own document through this (as zhoki's answer suggests by using context.auth.uid), then that'd be the equivalent of these security rules:
service cloud.firestore {
match /databases/{database}/documents {
match /{collection}/{userId} {
allow write: if request.auth != null && request.auth.uid == userId;
}
}
}
If this is the use-case you're looking for, I'd again recommend using security rules to secure it and bypass Cloud Functions for a simpler and cheaper solution.
In both of the cases above the {collection} allows the user to update documents in any collection, since that is what your Cloud Functions code also seems to do. It is much more common to limit the update to a specific collection, in which case you'd replace {collection} with just that collection name.

Getting "Error: Invalid IdP response/credential" trying to add FB user on Firebase Auth list

I've been working on implementing Facebook & Google sign-in method in an app based on Expo.
Having Firebase connected with my app, I want the user to be listed in Firebase Users. While Google sign-in seems to work fine, Facebook gives an error as stated below from firebase.auth().signInWithCredential(credential):
[Error: Invalid IdP response/credential: http://localhost?id_token=EAAGUBzIZAgb0BABAkdrj5NcUcvljcsLeNBQlV5VUZAZC7M8e7sSRg2MkqlFCuD7tKjin4uZBep5gSs20oAo8fXKiUqq2deEbUl6HoaAUskTda7x49VCqqcbYh1W3566fMZBtRFB5S3fRV7D41AGVGPMAck91l1KiFCzQzCGtSf5g6ZBKoyHw03LOVONcOiwVZB4vXVcGPYmIzL3RuzzztdBNLRql5ndSk0ZD&providerId=facebook.com]
Here's the relevant part of the code:
Firebase.js
// imports, configs and exports omitted
export const facebookProvider = new firebase.auth.FacebookAuthProvider;
LoadingScreen.js (the sign-in part)
import firebase, { googleProvider, facebookProvider } from "../firebase";
import * as Facebook from "expo-facebook";
const LoadingScreen = ({ navigation }) => {
const signInWithFacebook = async () => {
try {
await Facebook.initializeAsync("444233602924989");
const { type, token } = await Facebook.logInWithReadPermissionsAsync(
"444233602924989",
{
permissions: ["public_profile", "email"]
}
);
if (type === "success") {
const credential = facebookProvider.credential(token);
onSignInFB(credential);
} else {
Alert.alert('FB sign-in failed')
}
} catch ({ message }) {
console.log(`Facebook 1 Error: ${message}`);
Alert.alert(`first ${message}`);
}
};
const onSignInFB = credential => {
firebase
.auth()
.signInWithCredential(credential)
.then(function() {
navigation.navigate("Home");
})
.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;
});
};
};
I've logged some of the elements as follows:
While none of the error properties were available, the error code was auth/internal-error. Console.logging the error itself produced the error Invalid IdP response/credential.
The token from logInWithReadPermissionsAsync was a valid access token (string)
The credential from facebookProvider.credential(token) was:
Object {"oauthIdToken": "EAAGUBzIZAgb0BAKgeMa5E7qHm8WNJYv5SeuQ8DHyhkUlAnkMhE7niu6tx3e2amSMHSEqG9B0MV4a9dygwgjs337PR7AA3M4PZB2F6x6n1FwAEyZBKhZBpOSE2OWQ9dJipirpafg61TKX36hnKIzaIcwRkjs8YYRBbDuLnZAhJzWst3ZBM5tafwxYKumv2F4kYdexxZAXqb1nosnwYodNvB9bstkcaBrfB8ZD",
"providerId": "facebook.com",
"signInMethod": "facebook.com",
}
firebase.auth().currentUser was null.
I've also gone through my own sanity checklist of possible mistakes below, but couldn't find any:
FB is enabled in Firebase Auth Sign-in
AppId & App Secret is correctly copied from FB app to Firebase(and also the redirect URI)
Pass in the access token from Facebook.logInWithReadPermissionsAsync to Facebook's access token debugger and see if anything is wrong (e.g. expired or invalid)
Check if an actual credential is returned from facebookProvider.credential(token) and passed to signInWithCredential
It would be great to find out the source of the problem! (one possibility might be the access token returned from logInWithReadPermissionsAsync is identical with the oauthIdToken property in the credential object, but I haven't manually put or changed any one of those.)
Thanks for reading :)
I banged my head against this today and the solution was to pass an object in to the credential call, even though the docs say to pass in a string. In addition, the object property needs to be keyed as accessToken.
So instead of:
const credential = facebookProvider.credential(token);
try:
const credential = facebookProvider.credential({ accessToken: token });
Based on the docs, I have no idea why that key is needed or why it has to be an object, but it worked in my case. Hope it helps!

Categories

Resources