React-Native + Apple sign-in + Firestore: permission-denied - firebase

I'm trying to add Apple Sign-In to my project which is based on react native and firestore. Authentication flow itself works fine but firestore security rules reject my request when I try to create a user profile afterwards.
Firebase security rules:
rules_version = '2';
service cloud.firestore {
match /users/{userId} {
allow create:
if request.auth != null;
...
}
...
}
Simplified React Native code:
import { firebase } from './config';
import { firebase as RNFBAuth } from '#react-native-firebase/auth';
// Step 1
const credential = RNFBAuth.auth.AppleAuthProvider.credential(token, nonce);
// Step 2
RNFBAuth.auth().signInWithCredential(credential).then((response) => {
if (response.additionalUserInfo.isNewUser) {
// Step 3
firebase.firestore()
.collection('users')
.doc(uid)
.set({
// profile details
})
.then(() => {
// update local state
})
.catch((_error) => {
console.log(_error + ": " + _error.code);
});
}
});
Step 3 is failing with error code FirebaseError: The caller does not have permission: permission-denied.
Error is gone when Firestore security rules are downgraded to "allow create: if true". Unfortunately it does not fly for me for obvious reasons.
My guess is firebase/firestore does not know that user completed authentication via firebase/auth package thus request in "Step 3" is being send as unauthenticated one. Any ideas how to sync them?
Other Auth Providers like Google and Facebook are located at the main firebase package instead of firebase/auth thus same problem does not apply for them:
const credential = firebase.auth.FacebookAuthProvider.credential(token);
const credential = firebase.auth.GoogleAuthProvider.credential(token);
const credential = RNFBAuth.auth.AppleAuthProvider.credential(token, nonce);
Any ideas how to solve it?

Eventually the problem has been found - incompatible package versions. I've upgraded all firebase packages and #invertase/react-native-apple-authentication to the latest versions and everything seems to work fine now.

As sugested in the comment you should use the onAuthStateChanged listener:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
// User is signed in, see docs for a list of available properties
// https://firebase.google.com/docs/reference/js/firebase.User
var uid = user.uid;
// ...
} else {
// User is signed out
// ...
}
});
That way you have a general solution for all auth providers. It would not matter wich one you use. The trigger will be fired asap a user is signed in. I also use that method in all of my apps to sync user data.
You can read more about it here.

Related

Second Firebase project cant access first project Firestore

I'm building an e-commerce website and admin dashboard.
Website using its own hosting, Firestore and authentication.
Admin dashboard own hosting and authentication, but using website Firestore .
The security rules are not letting me in. When I change to admin dashboard project id to the website one, I can access the website Firestore.
I'm thinking that it's know I am coming from the second project that why it didn't allow me to access the website Firestore.
How can I allow second project to access the first project Firestore
function authorize() {
return request.auth != null &&
request.auth.token.email_verified;
}
function matchOwnID(userID) {
return request.auth.uid == userID;
}
function authAdmin() {
return authorize() &&
exists(/databases/$(database)/documents/admin/$(request.auth.uid));
}
match /admin/{adminID} {
allow get : if authAdmin() && matchOwnID(adminID); <--cant access from second project but first project can access.
// allow get : if true; <-----can access from second project.
allow list : if false;
allow write: if false;
}
How can I allow second project to access the first project Firestore
You cannot have users authenticated through the Auth service of one Firebase project accessing the Firestore database of an another Firebase project which has security rules based on the user's authentication state. This is not possible, since you cannot share the Firebase ID tokens across different projects.
One solution is to configure the two projects in your app, as explained in the "Configure multiple projects" doc.
Then you will be able to login with the two different user accounts corresponding to the two Firebase projects.
For the web, it could be done as follows:
const primaryAppConfig = {
apiKey: '.....',
authDomain: '.....',
// ...
};
firebase.initializeApp(primaryAppConfig);
const secondaryAppConfig = {
apiKey: '.....',
authDomain: '.....',
// ...
};
var secondaryApp = firebase.initializeApp(
secondaryAppConfig,
'secondary'
);
firebase
.auth()
.signInWithEmailAndPassword(email1, password1)
.then(() => {
secondaryApp.auth().signInWithEmailAndPassword(email2, password2);
})
.then(() => {
firebase
.firestore()
.collection('...') // Firestore collection from the Primary Project
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, ' => ', doc.data());
});
});
secondaryApp
.firestore()
.collection('...') // Firestore collection from the Secondary Project
.get()
.then(function (querySnapshot) {
querySnapshot.forEach(function (doc) {
// doc.data() is never undefined for query doc snapshots
console.log(doc.id, ' => ', doc.data());
});
});
});
Having said that, are you 100% sure that you need to use the authentication services of the two Firebase Projects? A common approach is to have your main app in a first Firebase Project, using the Auth service of this project. And then use a second Firebase project just for the hosting of the Admin dashboard app. But, in the Admin dashboard app, you point to the first project.
Another possible approach is to set up two Firebase Hosting sites in a single Firebase project, see the doc for more details.

This operation is not supported in the environment this application is runnung on [duplicate]

I develop a react-native (expo) mobile app and try to sign in with a google account to firebase, but I get an error:
"auth/operation-not-supported-in-this-enviroment. This operation is not supported in the enviroment this application is running on. "location.protocol" must be http, https or chrome-extension and web storage must be enabled"
Code:
loginGoogle() {
var provider = new firebase.auth.GoogleAuthProvider();
provider.addScope('profile');
provider.addScope('email');
firebase.auth().signInWithPopup(provider).then(function(result) {
var token = result.credential.accessToken;
var user = result.user;
return true;
}).catch(function(error) {
alert(error.code + '\n' +
error.message + '\n' +
error.email + '\n' +
error.credential);
return false;
});
}
signInWithPopup is not supported in react-native. You will need to use a third party OAuth library to get the OAuth ID token or access token and then sign in with Firebase:
const cred = firebase.auth.GoogleAuthProvider.credential(googleIdToken, googleAccessToken);
firebase.auth().signInWithCredential(cred)
.then((result) => {
// User signed in.
})
.catch((error) => {
// Error occurred.
});
Firebase does not support signInWithPopup in a React Native environment.
You can view a full list of supported environments on this page.
You can also submit a feature request for extended Firebase support for React Native here.
If you are using expo bare workflow or simple React native cli (or in simple words which contain android and ios folder) then simply use "React Native Firebase" library.
Here is the link https://rnfirebase.io/
But if you are using expo managed workflow(which donot contain android and ios folder ) then you have to follow below steps .
1.setup google developer account
use this guide to setup : https://docs.expo.dev/versions/latest/sdk/google/
Note that: use host.exp.exponent as the package name.
Another problem you may face in this step is generation of hash,which I also faced,the reason for that error is java dev kit(JDK) is not install ,so do install it before proceeding to this step.
2.Setup Firebase account
Simply setup firebase project as you set before, enable google sign in service
but this time the only change is you have to add client ID of your google developer account in (safest client id field) which will popup once you click on edit Google signin in firebase
look like this
3.Coding Part
import * as Google from 'expo-google-app-auth'; //imported from expo package
import {
GoogleAuthProvider,getAuth
} from 'firebase/auth';
import { initializeApp } from "firebase/app";
import { firebaseconfig } from '[your firebase credentials]';
const app=intitializeApp(firebaseconfig)
const auth=getAuth(app);
async function signInWithGoogleAsync() {
try {
const result = await Google.logInAsync({
androidClientId: 'cliend id from google dev console',
iosClientId: 'client id from google dev console for ios app(if you setup)',
scopes: ['profile', 'email'],
});
if (result.type === 'success') {
console.log(result)
const credential = GoogleAuthProvider.credential(result.idToken, result.accessToken);
// Sign in with credential from the Facebook user.
signInWithCredential(auth, credential)
.then(async result => {
console.log(result)
})
.catch(error => { console.log(error) });
return result.accessToken;
} else {
console.log("cancelled by user")
return { cancelled: true };
}
} catch (e) {
console.log(e);
return { error: true };
}//
}

create user with self-specified uid

I am using flutter with firebase to manage my users, and in this link, it says you can specify the uid during user creation: https://firebase.google.com/docs/auth/admin/manage-users#create_a_user
My question: What's the equivalent in dart/ flutter? I understand firebase auto-generates one for you, but in my use case I need to be able to specify mine.
For flutter, I am only aware of createUserWithEmailAndPassword method but it does not have a 'uid' argument.
FirebaseAuth.instance.createUserWithEmailAndPassword(email: null, password: null)
In the link above, however, they provided an example (node.js) with such methods.
admin.auth().createUser({
uid: 'some-uid',
email: 'user#example.com',
phoneNumber: '+11234567890'
})
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log('Successfully created new user:', userRecord.uid);
})
.catch(function(error) {
console.log('Error creating new user:', error);
});
You can fully control the creation of Firebase Authentication users by implementing a custom provider. But as you can probably imagine, this is a sensitive operation, so the code requires that you have full administrative access to the Firebase project. For that reason, you can't run this type of operation in your Flutter app, but must run it on a trusted environment, such as your development machine, a server you control, or Cloud Functions.
Typically this means:
you'll gather the user's credentials in your Flutter app
send them (securely) to a custom endpoint on the server
and there validate the the user credentials are correct
and use the Admin SDK to create a token for the user
that you then send back securely to the Flutter app
There is no such option for any of the Firebase the client SDKs on any platform (Android, iOS, web, Flutter, etc).
Firebase Admin is a server SDK, and trusts that the code calling its methods is privileged and running in a secure and trusted environment. This is considered to be safe for inventing UIDs. The client SDKs are run on user devices, which is considered untrusted and could be compromised. In that case, the Firebase Auth product has to come up with an appropriate UID for the user.
Use the firebase cloud functions
const functions = require("firebase-functions");
const admin = require("firebase-admin");
admin.initializeApp();
exports.createUser1 = functions.https.onCall(async (data, _context) => {
try {
const user = await admin.auth().createUser({
uid: data.uid,
phoneNumber: data.phoneNumber,
disabled: false,
}); return {response: user};
} catch (error) {
throw new functions.https.HttpsError("failed to create a user");
}
});
then request from flutter app
{
"data":
{
"uid": "12345678",
"phoneNumber": "+905378227777",
"disabled": false
}
}

Firestore permission denied when using signInWithCredential(), React Native Expo

firebase.initializeApp(config);
const db = firebase.firestore();
const googleSignIn = async () => {
return await Expo.Google.logInAsync({
androidClientId,
iosClientId,
scopes: ['profile', 'email'],
});
};
const firebaseLogin = async (accessToken) => {
const cred = firebase.auth.GoogleAuthProvider.credential(null, accessToken);
await firebase.auth().signInWithCredential(cred).catch(console.error);
const idToken = await firebase.auth().currentUser.getIdToken(true).catch(console.error);
};
await firebaseLogin(googleSignIn().accessToken);
db.collection("any").doc().set({test: "OK"})
I get a permission denied error when trying to write to Firestore using a request.auth.uid != null; security rule, but when I replace it with true it works.
It seems that the Firestore component of the web SDK does not send authentication details, even though the API on the client reports Firebase is logged in, and the user last login date appears in the web GUI.
Do I need to pass authentication details to the Firestore component when logging in directly with Google (instead of using the Firebase login APIs)?
The code is running in a React Native app via Expo.
Another example that gets a permission denied:
firebase.auth().onAuthStateChanged((user) => {
if (user) {
firebase.firestore().collection("any").doc().set({test: "OK"});
}
});
Rules
// This is OK:
service cloud.firestore {
match /databases/{database}/documents {
match /any/{doc} {
allow read, write: if true;
}
}
}
// Permission denied
service cloud.firestore {
match /databases/{database}/documents {
match /any/{doc} {
allow read, write: if request.auth.uid != null;
}
}
}
Related
Firebase Firestore missing or insufficient permissions using Expo (React Native)
https://forums.expo.io/t/firestore-with-firebase-auth-permissions-to-read-write-only-to-signed-in-users/5705
This solution, and possibly this whole issue, may be specific to React Native.
In a similar question, Jade Koskela noticed that requests were missing the Origin header, and applies a patch to React's XHRInterceptor to work around the missing auth object:
const xhrInterceptor = require('react-native/Libraries/Network/XHRInterceptor');
xhrInterceptor.setSendCallback((data, xhr) => {
if(xhr._method === 'POST') {
// WHATWG specifies opaque origin as anything other than a uri tuple. It serializes to the string 'null'.
// https://html.spec.whatwg.org/multipage/origin.html
xhr.setRequestHeader('Origin', 'null');
}
});
xhrInterceptor.enableInterception();

Any way to use Firebase google authentication in expo (create-react-native-app) without "eject" project

As the question, for Login with google in firebase need to set google-service but if you create new react-native project with create-react-native-app there will have no "android" or "ios" folder (accept used "eject") so, anyone have a suggestion for me?
However I've no idea for how to setting google-service in my project too (even I "eject" the project).
#brentvatne 's answer is a bit out of date. Here's how I got it working on Expo v27
Important bit: you can get your client ids with these instructions.
Just select your firebase app from the project dropdown on the google page.
const _loginWithGoogle = async function() {
try {
const result = await Expo.Google.logInAsync({
androidClientId:"YOUR_ANDROID_CLIENT_ID",
iosClientId:"YOUR_iOS_CLIENT_ID",
scopes: ["profile", "email"]
});
if (result.type === "success") {
const { idToken, accessToken } = result;
const credential = firebase.auth.GoogleAuthProvider.credential(idToken, accessToken);
firebase
.auth()
.signInAndRetrieveDataWithCredential(credential)
.then(res => {
// user res, create your user, do whatever you want
})
.catch(error => {
console.log("firebase cred err:", error);
});
} else {
return { cancelled: true };
}
} catch (err) {
console.log("err:", err);
}
};
It isn't necessary to make any changes to the android or ios folders in order to support Google sign in with firebase on an app built with Expo.
Follow the guide for configuring Google auth on the Expo docs
Use the approach described in Expo's Using Firebase guide, where it describes how to authenticate with Facebook, and swap out Google where needed.

Resources