How to add metadata to Firebase authentication - firebase

I need to pass a custom value (device_id) during google signin with firebase authentication. This value is later obtained from cloud functions by listening for authentication event triggers and then the value is added to Firestore
I understand that you can pass values as query parameters for http triggers. However I only need to pass and get the value during and after authentication in my case. Hence is there some sort of auth.addMetaData(metadata) function in firebase authentication?
I need to be able to retrieve the custom data after an auth trigger just like we can do user.email. I need something like user.custom_data

Although Doug mentions Firebase Custom Claims, I think it’s worth extra documentation because it does allow you to add simple metadata to a Firebase User object.
Important notes
Big caveat: Custom claims are only refreshed when the user logs in. So an isAdministrator claim would require the user to logout/login before it is activated.
Firebase recommends “Use custom claims to store data for controlling user access only. All other data should be stored separately via the real-time database or other server side storage.”
Set metadata (server only)
Here’s an example on how to set device_id on a Firebase User object (on the server using firebase-admin):
await admin.auth().setCustomUserClaims(uid, { deviceId })
Note: You can not set custom claims on the client.
Get metadata (server and client)
Then to retrieve the the device_id from the User on the server:
const userRecord = await admin.auth().getUser(uid)
console.log(userRecord.customClaims.deviceId)
…and on the client:
const idTokenResult = await firebase.auth().currentUser.getIdTokenResult()
console.log(idTokenResult.claims.deviceId)
Use metadata in Firebase Security Rules
The neat thing is that custom claims are also available in Firebase Security Rules. This (slightly unrealistic) example only allows users with deviceId === 123 to see the data:
{
"rules": {
"secureContent": {
".read": "auth.token.deviceId === 123"
}
}
}
More information
Official docs: https://firebase.google.com/docs/auth/admin/custom-claims
Deep dive: https://medium.com/google-developers/controlling-data-access-using-firebase-auth-custom-claims-88b3c2c9352a
A clever pattern of synching custom claims with a Firebase database collection: https://medium.com/firebase-developers/patterns-for-security-with-firebase-supercharged-custom-claims-with-firestore-and-cloud-functions-bb8f46b24e11

Firebase Authentication doesn't support any sort of extra data provided by the client. The closest thing to metadata that gets stored per user by Firebase would be custom claims, however, the JSON blob stored there can only be set by privileged server-side applications.
If you need to store data per user, written by client apps, you should probably be using a database for that (Cloud Firestore or Realtime Database), protected by Firebase security rules, so that only the end user can read and write their own data. You could also use an HTTP type Cloud Function to pass data into your function to be recorded in a database.

Related

Firebase auth - how to un-protect listCollectionIds method in REST API

It seems that I can use the get method without any authorisation, but listCollectionIds needs some key to access, as I'm getting the 403 error without it.
I am able to access that data when I'm using the OAuth 2.0 on Google's API Explorer utility.
Firestore security rules are currently set to allow everything. Is there a way to disable authentication for listCollectionIds?
You cannot list collections using client SDKs or remove any restrictions on it. Only the Admin SDKs can access the listCollections() method using service accounts. The documentation says,
Retrieving a list of collections is not possible with the mobile/web client libraries. You should only look up collection names as part of administrative tasks in trusted server environments. If you find that you need this capability in the mobile/web client libraries, consider restructuring your data so that subcollection names are predictable.
That being said, the best you can do is store list of collections (or sub-collections) in a document somewhere else in the database.
Other way around this would be creating a cloud function that'll fetch list of collections on behalf of your users.
exports.getCollections = functions.https.onCall(async (data, context) => {
// Authorize user if needs
const collections = await admin.firestore().listCollections()
return collections
});

How can I send the parameter with the firestore get method?

My project is creating a website using firebase, but it is using the internal authen micro service instead of Firebase authen.
Every time the user logs in, the internal authen service will generate a token (I call it client_id), and send it to the client.
This token is also stored in the user collection.
Please help me how to write rule for the user via this client_id.
If it is write role, I can send the client_id via mothod update, delete, create and get it out by
request.resource.data.cliend_id and check it.
But if it is read role, I don't have any way to send to the client_id via the get method to authenticate users
I even thought of replacing all the get methods with the update methods to pass the client_id
But the response of the update method is just the update_time, not the object I updated
Firebase RTDB & Firestore Rules uses their own auth only. In every request that firebase client library sends uid along with jwt token and other security parameters. These are fundamental to firebase's working and thus can't be replaced.
As Aleksey mentioned, you can try custom auth. It is specifically tailored for such use cases only.
There is no way to provide extra data to security rules for the purpose of limiting read operations. That would not be secure at all, because a client could simply fake whatever they want in the query.
You can only use information provided by Firebase Auth available in request.auth, the data in the document itself in resource.data, or the contents of other documents using get(). If you want to attach additional data to the user account, consider using custom claims.

Can Firebase Cloud Storage rules validate against Firestore data?

Can we use Firestore data to grant or restrict access to files hosted on Firebase Cloud Storage?
Exemple of what I would like to use as Firebase Security Rule
allow write: if get(/databases/mydbname/documents/guilds/$(guildID)).data.users[(request.auth.uid)] in ["Admin", "Member"];
Update (Oct 2022): it is now possible to access Cloud Firestore from within your Cloud Storage security rules with the new firestore.get() and firestore.exists() functions. See the blog post Announcing cross-service Security Rules and the documentation on enhancing Cloud Storage security rules with Cloud Firestore.
Previous answer below for reference:
There is currently no way to access different Firebase products from within the security rules of another product. See: is there a way to authenticate user role in firebase storage rules?
But it seems like you are trying to check group-membership of the user. Instead of looking that group-membership up in the database, I recommend that you model it as a so-called custom claim. Instead of (or in addition to) writing the membership to the database, you'll set the claim "user {uid} is a member of group {guild1}" into the user profile using the Firebase Admin SDK:
admin.auth().setCustomUserClaims(uid, {guild1: true}).then(() => {
// The new custom claims will propagate to the user's ID token the
// next time a new one is issued.
});
With that done, you can check the guild membership in the security rules:
allow read: if request.auth.token.guild1 == true;
(I'm not sure if/how you can model the guild membership as a map, I'll update this answer if that turns out to be the case.)
Firebase recently announced cross-service security rules that let's you access Firestore data in Firebase storage's security rules. You just need to use firestore. prefix before get() and exist() functions as shown below:
allow write: if firestore.get(/databases/(default)/documents/col/docId/).data.field == "value";
Firebase current supports only 1 database instance per project so the name must be (default) in path. It's not a wildcard as in Firestore rules so not $(database)
Update: As of 2022-09-28, Firebase introduced cross-service Security Rules, so the answer below is outdated. See #Dharmaraj's answer below for an example.
You can retroactively validate and delete the file after it's been uploaded using a cloud function trigger.
Warning: this technique is not bullet proof, as the invalid file will be stored in Cloud Storage temporarily or potentially forever if the Cloud Function trigger fails. My preference is to prevent the upload in the first place, but if the logic to determine permission resides in Firestore and can't be stuffed in custom claims, then this is currently the only way if you're uploading files using Firebase's Client SDKs. If you're building a mission critical system, you should upload the file to a Cloud Function and let the Cloud Function store the file in Cloud Storage instead.
When uploading a file, add some metadata indicating who's doing the upload:
const storageRef = ref(
storage,
`files/${fileName}`,
);
const uploadTask = uploadBytesResumable(storageRef, file, {
customMetadata: {
uploaderId: userId,
},
});
Set storage rule to ensure that the user identity metadata can be trusted:
match /files/{fileName} {
allow create: if request.auth != null &&
request.resource.metadata.uploaderId == request.auth.uid
}
Create a cloud function trigger that retroactively validates and deletes:
export const onFinalize = functions
.storage.object()
.onFinalize(async object => {
// We can trust object.metadata.uploaderId, so check Firestore if user is able to upload file
if (!(await canUploadFile(object.metadata.uploaderId, object.name))) {
await storage.bucket(object.bucket).file(object.name).delete();
throw new Error(
`Permission error: ${object.metadata.uploaderId} not allowed to upload ${object.name}`,
);
}
// Continue
});

How to make firebase database rule work with admin.database().ref (cloud function)

I'm working with Cloud Function for Firebase. When I use admin.database().ref then all the rules that applied to the database were ignored. With admin, I can do anything. To be clear:
I have a real-time database with have a set of rules such as; name must be string and length >= 50,...
It works when using the SDK, all the invalid data will be denied. But when I move to use firebase cloud function (to reduce work in client side by providing a set of https endpoints) it didn't work anymore.
So, I wonder if there is any way to make it work? I was thinking about:
find something replace for admin.database() (took a look on
event.data.ref already but this does not work in my case - HTTP request)
verify data in cloud function (not nice)
Could you give me some hints/clues?
This is the expected default behavior of the Firebase Admin SDKs: the code accesses the database with administrative privileges.
If you want to limit what the code can do, you can initialize it to run as a specific UID. From the documentation on Authenticate with limited privileges:
To get more fine-grained control over the resources a Firebase app instance can access, use a unique identifier in your Security Rules to represent your service.
...
Then, on your server, when you initialize the Firebase app, use the databaseAuthVariableOverride option to override the auth object used by your database rules. In this custom auth object, set the uid field to the identifier you used to represent your service in your Security Rules.
// Initialize the app with a custom auth variable, limiting the server's access
admin.initializeApp({
credential: admin.credential.cert(serviceAccount),
databaseURL: "https://databaseName.firebaseio.com",
databaseAuthVariableOverride: {
uid: "my-service-worker"
}
});
Please see the linked documentation for the complete example.

Cloud Functions for Firebase: How to read/write to database as an existing user?

I like to use firebase functions to test firebase rules I defined.
I would like to read/write to realtime database as an existing user to test if the rules work as expected.
I read in getting started page, I can write to realtime database as admin as follow:
admin.database().ref('/messages').push({original: 'some text'});
How can I do the same as a user I have created in firebase instead of as admin?
I believe when you get the Delta Snapshot from the triggered event that the current state of that snapshot is tied to the user. Since the Firebase team is providing you with the server-less environment, they attach the admin as well since it's in a secure location.
So just grabbing the ref from the current snapshot, should give you the ability to test the database rules. Just to clarify, I am talking about the snapshot.ref, and not the snapshot.adminRef.
Here is the reference from their documentation:
Returns a Reference to the Database location where the triggering write occurred. This Reference has the same end-user permissions as the client that did the write. So, if an unauthenticated client did the write, this Reference is unauthenticated. If the client that did the write is authenticated as a certain Firebase Auth user, this Reference is authenticated as that same user.

Resources