I am adding firebase Google OAuth to my Electron app - I have followed this tutorial: https://pragli.com/blog/how-to-authenticate-with-google-in-electron/.
The general idea is to launch the Electron app and on click of "Log in with google", create a UUID, write to a firebase realtime database entry indexed with this UUID and listen for changes to this entry. Then launch a browser and pass the UUID in the URL params. When the page loads, call a firebase cloud function (from the browser app) and pass the UUID to it. Inside the cloud function, the database entry can be referenced and an authentication token is written to the database.
This is where I create the database reference in my Electron app:
function createOAuthDatabaseEntry(
auth: Auth,
database: Database,
electronAuthUuid: string,
): DatabaseReference | undefined {
if (auth.currentUser) { // at this point in time the currentUser is null because they have not logged in
const OAuthDatabaseRef = ref(
database,
`ot-auth-codes/${auth.currentUser?.uid}/${electronAuthUuid}`,
);
return OAuthDatabaseRef;
}
}
And then listen for changes:
function listenForBrowserSignIn(
auth: Auth,
OAuthDatabaseRef: DatabaseReference,
): Promise<{
token: string;
unsubscribeFromOAuthDatabaseChanges: Unsubscribe;
}> {
return new Promise(resolve => {
const unsubscribeFromOAuthDatabaseChanges = onValue(
OAuthDatabaseRef,
snapshot => {
const token = snapshot.val();
resolve({ token, unsubscribeFromOAuthDatabaseChanges });
},
);
});
}
Here is my cloud function code:
exports.createAuthToken = functions.https.onCall(async (data, context) => {
if (!context.auth) return { status: 'error', code: 401, message: 'Not signed in' };
const decodedToken = await admin.auth().verifyIdToken(data.token);
const authToken = await admin.auth().createCustomToken(decodedToken.uid);
try {
await admin
.database()
.ref(`ot-auth-codes/${context.auth.uid}/${data.code}`)
.set(authToken);
functions.logger.info('-------------> data sucessfully written to database')
} catch (error) {
functions.logger.error('-------------> error writing to database')
}
});
My problem is that I'm trying to add security rule to the database. So far I have added these ones which are suggested in the firebase docs:
{
"rules": {
"some_path": {
"$uid": {
// Allow only authenticated content owners access to their data
".read": "auth != null && auth.uid == $uid",
".write": "auth != null && auth.uid == $uid"
}
}
}
}
This is where the cloud function is called when Electron launches the browser application which is a react app:
function SignIn() {
useEffect(() => {
(async () => {
try {
const app = initializeApp(firebaseConfig);
const functions = getFunctions(app);
const provider = new GoogleAuthProvider();
const auth = getAuth();
const result = await getRedirectResult(auth)
if (!result) {
signInWithRedirect(auth, provider)
} else {
if (!result.user) {
return
}
const params = new URLSearchParams(window.location.search)
const token = await result.user.getIdToken()
const code = params.get("ot-auth-code")
const data = { token, code };
const createAuthToken = httpsCallable(functions, 'createAuthToken');
await createAuthToken(data)
}
} catch (error) {
console.error('ERROR ----------------------->', error);
}
})()
}, [])
My problem is is that I can't use these specific rules to protect the data because at the time of first creating the reference to the database, the user is not logged in and so a firebase user uid is not available.
My question is, is it safe to remove user specific database rules for this use case or is there something else I could use instead of the uid to protect the data?
You're going about this the wrong way. Your function should return the token directly to the calling client code as its output. The client can then use that to sign in. You definitely do not want to store auth tokens unprotected. That would be a security problem.
I suggest reviewing the pattern described in the documentation.