How to prove to the server that I as a client am logged in with the given uid? - firebase

Heres my problem:
I wan't to be able to create new users for my website, from my website. This is only aloud though, if I have the "isAdmin" flag set to true in the realtime db under /users/myid.
Generally I would have done this with a security rule but the problem here is that I need to use the admin SDK, since the normal "createNewUser" method signs in to the newly created user automatically. I as an admin though, want to be able to create a new user and stay logged in as myself. So what I wan't to do is use ajax post request to my server with my uid und the new userdata which is to be created. My server then checks if the given uid has the isAdmin flag and if so creates the user with the firebase admin SDK which provides such a method.
But, anyone, if they have an admins uid, could hit up that request and create a new user. (My clients definetely get uid's from other users).
So how would I go about proving to the server that I am actually logged in with that uid.
From my understanding, tokens are used to be able to write to the database, but I don't need that permission, I just need to prove that I'm actually logged in with that uid.
Is there something I'm missing? Thanks a lot guys!

Was easier then I thought. This will generate a token on the client side:
firebase.auth().currentUser.getToken(true).then(function(token) {
// send request to server with generated token
}).catch(function(error) {
// handle error
});
Which I can then verify on the server like so:
admin.auth().verifyIdToken(idToken)
.then(function(decodedToken) {
var uid = decodedToken.uid;
// user is logged in
}).catch(function(error) {
// user is not logged in, or other error occured
});
Taken from https://firebase.google.com/docs/auth/admin/verify-id-tokens

Related

Firebase auth overriding user providers (email + password, phone) when signing-in with Google

As we have seen in another posts related to this situation and github issues, the expected behavior is that Google IDP overrides other non trusted providers related to the same email as an example, another account with the same email + password (non-verified).
Trying to understand Firebase Authentication one account per email address and trusted providers
Firebase Overwrites Signin with Google Account
https://github.com/firebase/firebase-ios-sdk/issues/5344
https://groups.google.com/g/firebase-talk/c/ms_NVQem_Cw/m/8g7BFk1IAAAJ
So, ok, according to google that's the expected behavior.
Our questions comes when we go to the documentation and there's an example of a user login in with google and getting this error auth/account-exists-with-different-credential just because there's another account created with email+password with the same email. Then, they recommend to catch the error, check the user email related login methods and ask the user to login with the other provider and then link to google.
Does this make sense ? If they say the expected behavior is that google as a trusted provider will override the others (this is what happens to us) how is possible that the case of the code example would even occur ?
https://firebase.google.com/docs/auth/web/google-signin#expandable-1
// Step 1.
// User tries to sign in to Google.
auth.signInWithPopup(new firebase.auth.GoogleAuthProvider()).catch(function(error) {
// An error happened.
if (error.code === 'auth/account-exists-with-different-credential') {
// Step 2.
// User's email already exists.
// The pending Google credential.
var pendingCred = error.credential;
// The provider account's email address.
var email = error.email;
// Get sign-in methods for this email.
auth.fetchSignInMethodsForEmail(email).then(function(methods) {
// Step 3.
// If the user has several sign-in methods,
// the first method in the list will be the "recommended" method to use.
if (methods[0] === 'password') {
// Asks the user their password.
// In real scenario, you should handle this asynchronously.
var password = promptUserForPassword(); // TODO: implement promptUserForPassword.
auth.signInWithEmailAndPassword(email, password).then(function(result) {
// Step 4a.
return result.user.linkWithCredential(pendingCred);
}).then(function() {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
return;
}
// All the other cases are external providers.
// Construct provider object for that provider.
// TODO: implement getProviderForProviderId.
var provider = getProviderForProviderId(methods[0]);
// At this point, you should let the user know that they already have an account
// but with a different provider, and let them validate the fact they want to
// sign in with this provider.
// Sign in to provider. Note: browsers usually block popup triggered asynchronously,
// so in real scenario you should ask the user to click on a "continue" button
// that will trigger the signInWithPopup.
auth.signInWithPopup(provider).then(function(result) {
// Remember that the user may have signed in with an account that has a different email
// address than the first one. This can happen as Firebase doesn't control the provider's
// sign in flow and the user is free to login using whichever account they own.
// Step 4b.
// Link to Google credential.
// As we have access to the pending credential, we can directly call the link method.
result.user.linkAndRetrieveDataWithCredential(pendingCred).then(function(usercred) {
// Google account successfully linked to the existing Firebase user.
goToApp();
});
});
});
}
});
There's another example with the same structure in the flutter docs:
https://firebase.google.com/docs/auth/flutter/errors#handling_account-exists-with-different-credential_errors
Is this a contradiction in the documentation ? Again, if Firebase will always give priority to the trusted IDP (Google email) in this case, how is it possible to get this error if the other provider will be deleted (at least when having account linking activated - single account per email activated)
At least this is our case. We create an account with email & password and then try to login with google with the same email and what happens is that the email&password account is overwritten by the new google provider.
Unfortunately, you can't change it. If a user with #gmail.com email and password authentication updates their profile picture and then later logins with Google then the profile picture and any other information will be overwritten with the data from Google. The only option is to create a user record in the database that gets populated with the user data (displayName, photoURL etc) when the user is created for the first time. You then always use the data from this record instead of the default user object that is returned by the authentication.
The other advantage of creating a record is that you can attach a listener to it. That way if the user changes their details then it gets reflected everywhere.

How to verify custom auth claim upon login?

Similar to Uber, I have two applications, one for clients and one for drivers. Is it possible to know which role type the user has upon login? For instance, if I have a client account and I try to log in on the driver's application I should get the error: "client accounts cannot be used to log into the driver application".
Let's say I stored the user's account type (driver or client) in a custom auth claim, would it be possible to access that while firebase auth is verifying the email and password, or does the user have to log in successfully before I can verify the value of the custom auth claim?
Essentially, if the user tries logging into the wrong application, I want it to come back as an error without actually logging them in. So far I've only been able to check for this after the user logs in using getIDTokenResult.
Any help is appreciated! :)
Essentially, if the user tries logging into the wrong application, I want it to come back as an error without actually logging them in.
You seem to be mixing authentication (the user enters credentials that prove who they are) with authorization (the user is allowed to do certain things based on who the are). Firebase Authentication solely is concerned with the former: allowing the user to sign in once they enter the correct credentials for their account. Once the user is signed in, your application code can then determine whether they're allowed to perform certain actions.
For your specific use-case for example, the idiomatic approach is to:
Sign the user in to Firebase Authentication.
Check whether their token contains the necessary claim for the app they're trying to use.
If so, allow them to continue to the main screen of your app.
If not, inform them of that fact and don't allow them to continue.
As you can see here, it is your application logic that handles all authorization logic, while Firebase takes care of the authentication.
The user must be logged in before checking the claims and anyways you cannot prevent anyone from logging in if it's a same firebase project. You should check the claim after login and if the user has logged into wrong application, just force them to logout. Security Rules can be used to prevent unauthorized access.
firebase.auth().signInWithEmailAndPassword().then(async ({user}) => {
const claims = await user.getIdTokenResult()
// check for claim
// if not valid then logout or redirect to relevant pages
await firebase.auth(can ).signOut()
})
You can show your error alerts after signing out.
If you really want to check the claim before logging the user in then you would have to use cloud functions which checks claims for the entered email but this method may not be useful in other sign in providers such as Google or Facebook.
Although I won't recommend using Cloud functions just to check the claims before users logs in as it just can be bypassed on the frontend and as mentioned above, forcing the user to logout should be enough. But here's a cloud function you can use to check the claims.
exports.checkClaim = functions.https.onCall((data, context) => {
const {email} = data;
return admin
.auth()
.getUser(uid)
.then((userRecord) => {
const {customClaims: {driver, client}} = userRecord;
if (driver) return {role: "driver"}
if (client) return {role: "client"}
return {error: "No role found"}
})
.catch((error) => {
console.log('Error fetching user data:', error);
});
});
Then call the function before you run the signInWithEmailAndPassword method.
const checkUserRole = firebase.functions().httpsCallable('checkClaim');
checkUserRole({ email: "user#domain.tld" })
.then((result) => {
const {role, error} = result;
if (error) {
alert("Something went wrong. No roles found")
} else {
console.log(`Your role is: ${role}`)
}
});
Again as mentioned above this sounds a bit overkill but if it's necessary or you prefer to do it that way then you use this function.

Is there any way to get firebase Auth UID without login?

What i want to know is without login get Auth User UID from firebase
that already registered user
I already read firebase document but all the explain focus on when user logged only at that time can get UID...
but i just want to get UID and user email address without login situation
Is there any way?
Nope, there is no way using firebase authentication to retrieve the uid or email is the user is currently not logged in.
The only other way to actually retrieve them, is to use firebase database. So, when the user registers in your application, you also send the data of the user to the database and then you will be able to retrieve the data later on even if he is not logged in.
For example (on register) you can send this data to the database:
users
userId
email : email_here
name : name_here
Your question is not clear.
You can create the user via the Admin SDK if you know the user's credentials. You can also lookup an existing user's information by email or uid with the Admin SDK.
Learn more about this from the official docs.
You can apply a trick to do this. Though this is not an optimal solution.
One thing you have to remember that, "There is no way to get a UID without login".
Create a dummy user from your backend with a dummy email and password.
Send the email and password from the backend to your client-side app. You can always hash the password if you want to give an extra security layer.
Now call signInWithEmailAndPassword function from firebase SDK and provide email and password which came from the backend.
The user is already registered in the backend, so now you can get the UID from your client app.
mAuth.signInAnonymously()
.addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
#Override
public void onComplete(#NonNull Task<AuthResult> task) {
if (task.isSuccessful()) {
// Sign in success, update UI with the signed-in user's information
Log.d(TAG, "signInAnonymously:success");
FirebaseUser user = mAuth.getCurrentUser();
Log.d("userstatus","user id is "+user.getUid());
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInAnonymously:failure", task.getException());
}
// ...
}
});

Prevent user account creation with sign in by email in firestore

I have setup a passwordless login using firebase sign in with email link. Everything is working as expected, however when the user receives the email and clicks the link to login they are automatically created as a registered user.
https://firebase.google.com/docs/auth/web/email-link-auth
After a user signs in for the first time, a new user account is
created and linked to the credentials...
This means anyone who makes a request in the login screen will get an email and get access to the site.
I am not sure if there is any configuration or setup that i need to complete in order to require that the user requesting the signup link are only checked against the users that are registered.
Something like
firebase.auth().sendLoginLinkToEmail(email,{url:...,handleCodeInApp:true}).then(() =>{
....
}, error =>{
// return if not an authenticated user
})
And if the email is not registered then it returns an error.
The idea is to have an administrator that creates users and then those created users just login with an email link ( no password )
Is this possible? To prevent firebase from creating an account with.signInWithEmailLink() ?
Passwordless email sign in allows the user to prove they have access to a certain mail box. It does inherently do nothing more than that. Once the user clicks the link, they are authenticated. Beyond enabling/disabling the entire sign-in provider, you cannot control who can sign-in/authenticate.
After that it is up to your application to determine what this user is allowed to do. This is a separate step, typically called authorization.
Firebase Authentication takes (as its name implies) care of authentication only. You will have to handle authorization elsewhere, depending on what services you provide the users access to.
What makes an email "registered" in your app? I.e. where does the admin create those users? For example, if you store the users in the Cloud Firestore in a collection allowed_users, with documents like this:
allowed_users: // collection
"arkade#domain,com": { ... } // document
"puf#domain,com": { ... } // document
Now you can limit that only allowed users can access other data with Firestore's server-side security rules. Say you have a collection of posts, you can allow only these users to read posts with:
service cloud.firestore {
match /databases/{database}/documents {
match /posts/{post} {
// Make sure a 'allowed_users' document exists for the requesting user before
// allowing any reads from the 'posts' collection
allow read: if exists(/databases/$(database)/documents/allowed_users/$(request.auth.email))
}
}
The syntax is a bit long, but your can see that is only allows the reading of a post if the current user's email address (request.auth.email) exists as a document in allowed_users.
In rules version 2 of the Firestore rules, you access the current user's email address a little differently. You can do it via request.auth.token.email. The example below also shows how you can get a boolean property in the current user's document, if you identify that user by email:
allow write: if get(/databases/$(database)/documents/users/$(request.auth.token.email)).data.admin == true;

Creating temporary anonymous users in Firebase

I'm trying auto-generate user accounts that I can save data with, and later promote those to proper username/password accounts. Any ideas on the best way to do that?
It wasn't clear to me whether or not I could switch auth providers from anonymous to password.
You can start by creating an anonymous login with Firebase, and then store that auth "uid" key to some sort of session or cookie (depending on what framework you're working with). While the client is anonymous, you can link your saved data to this anonymous "uid".
To transfer the data over to the user after they login, you'll need to use Firebase's onAuth() listener. This will notify you when the user's auth data changes. Once you have the new authenticated uid, you can link your stored data to that new uid and delete your anonymous session.
Here's the modified sample from Firebase docs:
var firebaseRef = new Firebase('https://samplechat.firebaseio-demo.com');
firebaseRef.onAuth(function(authData) {
if (authData) {
// Client is logged in, so check if it's anonymous or a real user
if (authData.provider === 'anonymous') {
// user is anonymous
// save authData.uid to some local session info, to keep track of data
} else {
// user is logged in
// transfer any data that you had stored from the anonymous session to
// this new authUser.uid
} else {
// Client is unauthenticated
// This would be a good spot to create the anonymous login
firebaseRef.authAnonymously(function(error, authData) {
if (error) {
// handle login failure
} else {
// you are now anonymously logged in, but you really don't need to do anything
// here, because your onAuth() listener is already going to catch this auth change
}
}
});
Firebase doesn't tell you what the previous uid was when the user changes their auth info, so you really need to store that anonymous uid somewhere. You could also do the whole thing without ever creating the anonymous login, by just storing session data until the user logs in, but the anonymous login provides more consistency.

Resources