Firebase Auth verify this user - firebase

I am currently verifying my user using the Auth JS SDK and Admin Auth SDK combined. I am doing in the following approach:
In the front-end:
firebase.auth().onAuthStateChanged(function (user) {
if (user) {
var current_user = firebase.auth().currentUser;
current_user.getIdToken(true).then(function (idToken) {
$.getJSON('/firebase_token', { token: idToken }, function (user) {
In the back-end:
router.get("/firebase_token", (req, res, next) => {
admin.auth().verifyIdToken(req.query.token).then(function(decodedToken) {
res.send(decodedToken);
})
})
I am wondering if this is a secured approach, because the user can just send whatever token they want from the front-end. For example, an invalid user can send a valid token they copied from a valid account to pass the token verification.
I am wondering if in the admin SDK. There is a way to detect the currently signed in user. In other words, detect this user who is using this instance of the app with the admin SDK?

I am wondering if this is a secured approach, because the user can just send whatever token they want from the front-end. For example, an invalid user can send a valid token they copied from a valid account to pass the token verification.
Yes, that's possible. But then again, if the user got access to a token, that means they probably are the user represented by that token, or they know the credentials of that account. That's not a problem at all - this is the way authentication systems work.
I am wondering if in the admin SDK. There is a way to detect the currently signed in user. In other words, detect this user who is using this instance of the app with the admin SDK?
No, the Admin SDK can't possibly know what all is going on for all of the users using your application. The ID token is exactly the piece of information it needs to verify users. A valid token proves that the user is who they say they are.

Related

Firebase email/password authentication - how to require email verification?

Whenever I use the email/password authentication provider in Firebase, the provider sends a bearer token upon successful sign-up even though the emailVerified is false. Is there a way, out of the box, to configure the email/password auth provider to not send a bearer token (and return a 403 error) until the user has verified their email address?
Note that I'm aware of how to create a user, sign in a user, send a verification email, etc... using firebase v9.x via the methods createUserWithEmailAndPassword, signInWithEmailAndPassword, signOut, sendEmailVerification from firebase/auth. I'm just asking if there is a way to set the behavior of the provider without having to write my own handler function for this. I'd like this to behave like Cognito does whenever the email verification is required.
There is no way to require the user's email address to be verified before they can sign in to Firebase Authentication.
The closest you can get is by using email-link sign-in, which combines signing in and verifying the user's email address in one action.
But this is how you'll typically want to implement this in your application code:
User enters their credentials
You sign them in to Firebase with those credentials
You check whether their email address is verified
If not, you stop them from further using the app - and (optionally) send them a verification email.
Same with data access: if you have a custom backend code, you can check whether the email address is verified in the ID token there too, as well as in Firebase's server-side security rules.
As per the documentation, you can use blocking functions to require email verification for registration (only that it doesn't work):
exports.beforeCreate = functions.auth.user().beforeCreate((user, context) => {
const locale = context.locale;
if (user.email && !user.emailVerified) {
// Send custom email verification on sign-up.
return admin.auth().generateEmailVerificationLink(user.email).then((link) => {
return sendCustomVerificationEmail(user.email, link, locale);
});
}
});
exports.beforeSignIn = functions.auth.user().beforeSignIn((user, context) => {
if (user.email && !user.emailVerified) {
throw new functions.auth.HttpsError(
'invalid-argument', `"${user.email}" needs to be verified before access is granted.`);
}
});
generateEmailVerificationLink always returns the following error:
"err": {
"message": "There is no user record corresponding to the provided identifier.",
"code": "auth/user-not-found"
},
but the user is created anyway given that beforeCreate don't return an exception.
If you want to check by yourself just log the error:
return admin.auth().generateEmailVerificationLink(user.email)
.then((link) => {
functions.logger.info("link", {user: user, context: context, link: link})
})
.catch((err) => {
functions.logger.info("error", {user: user, context: context, err: err});
});
The createUserWithEmailAndPassword() will sign in user right after the account is created. Also there isn't any way to prevent users from logging in even if their email is not verified but you can actually check if email is verified in security rules or using Admin SDK to prevent users with unverified email from accessing your resources. You can use this rule in Firestore:
allow read, write: if request.auth.token.email_verified == true;
One workaround would be creating users using a Cloud function and Admin SDK which won't sign in users but do note that users can sign in.
If you want to prevent login unless the email is verified strictly, then you can disable account right after it is created. Now you may not be able to use sendEmailVerification() which requires user to be signed in at first place, you can always create your own solution for verifying email. The process might look something like:
Create a user and disable the account in a Cloud function
Generate some token or identifier for verifying email and send an email to user from same cloud function
Once the user visits that link and verifies the email you can enable it
Additionally, users can still create accounts by using REST API but you can disable sign ups so users can be created via Cloud function only which disables the user immediately.

Set custom claims & role-based access for specific Google sign-in account

My firebase functions app makes use of Firebase & Google for authentication, checking for valid domains to grant access.
My concern is that a rogue employee can come along and destroy sensitive data. Thus, I wish to set custom claims for each account access the site from "admin" user of sorts.
There is already 2 questions (here and here) relating to this, being able to set claims in the Firebase console but this isn't yet a feature
My question has 2 parts:
How can I determine who my "admin" user is (I know the email, how do I set this user as the admin user)
How can this admin user set permissions for these accounts.
Possible solution:
The only solution I can think of is, upon for login, the account is saved in firestore e.g. /portal-users/${email}/mode
Modes:
none: on initial registration, the "admin" user needs to accept this person as a valid portal user
user: valid system user
revoked: revoked email, cannot access system
admin: well, admin I guess
My implementation using Firebase Functions & express follows this flow:
When signing in to POST:/user/createSession, I read the Firestore database and set custom claims using this as a base:
// if you are wondering what happened to CSRF - I couldn't get that to work :(
const idToken = req.body.idToken.toString();
const expiresIn = 60 * 60 * 24 * 5 * 1000;
const auth = admin.auth();
auth.verifyIdToken(idToken).then(value => {
console.log("Token verified")
auth.setCustomUserClaims(value.uid, {mode: `insert mode here from firestore`}).then(value1 => {
return auth.createSessionCookie(idToken, {expiresIn})
.then((sessionCookie) => {
// Set cookie policy for session cookie.
const options = {maxAge: expiresIn, httpOnly: true, secure: true};
res.cookie('session', sessionCookie, options);
res.end(JSON.stringify({status: 'success'}));
}).catch((error) => {
console.error(error);
res.status(401).send('UNAUTHORIZED REQUEST!');
});
})
}).catch(reason => {
console.error("Unable to verify token");
console.error(reason);
res.status(401).send('INVALID TOKEN!');
});
When requesting a auth /any/route/with/private/data route, the following check should be done (roughly) this way when validating a request:
admin
.auth()
.verifySessionCookie(sessionCookie, true)
.then((decodedClaims) => {
// Where the magic happens
switch (decodedClaims.mode) {
case "none": {
// return to login screen with awaiting verification message
break;
}
case "revoked": {
// return to login screen with revoked permissions message
break;
}
case "user":
case "admin":{
// continue
break;
}
}
res.status(401).send('UNAUTHORIZED REQUEST!');
})
.catch((error) => {
// Session cookie is unavailable or invalid. Force user to login.
res.redirect('/login');
});
To manage users' permissions, the admin user has a special page to set modes for each user on the system (which will eventually call setCustomClaims(), see above).
Are there any issues or security problems I might face? (except the CSRF issue ofc)
A few things to be aware of. Custom claims are cached from the client, these can lead to valid tokens with expired information making false-positive modifications to your database. This is an inherit flaw with Firebase's Auth system being restful by design, to get around this you must revoke the users' Auth token when their custom claims have changed, there are several ways to handle this:
Propagate: https://firebase.google.com/docs/auth/admin/custom-claims#propagate_custom_claims_to_the_client
Revoke: https://firebase.google.com/docs/auth/admin/manage-sessions#revoke_refresh_tokens
The main issue is that the user will have to re-authenticate to update these changes and should only be used on extreme account-wide conditions, such as setting administrator privileges, etc. In contrast, Cloud Functions have the luxury of validating the user object and accessing auth directly for the updated claims but these are not reflected in Security Rules. (this would be my dream fix)
Since Firestore and Realtime do suffer from cached claims, it's often more viable to have these flags set on the user based on a database profile or 'role' table with their UID associated with the correct Role. doing multiple reads and writes is also helpful as these reads from within Firestore are Cached per request, up to a maximum of 10 unique documents.
Source: https://firebase.google.com/docs/firestore/security/rules-conditions#access_other_documents
Custom Claims currently is not a viable solution to Secure your database, only validation on Cloud Functions and user flags on the client. Using a combination of the above methods and Database roles is the best solution.
Finding the user
You can setup a onUser create trigger within cloud functions that checks the user's credentials and confirms it with your backend, ie: is the user auth'd from "google.com" and has a specific prefix, etc.
or if the user is already within your Firebase Auth collection, you can target them by UID or email - Email is not as secure compared to the user's UID, as this does not validate the auth provider.
One popular solution is a Cloud Function Extention that adds claims based on Firestore document changes
Source:
Cloud Function Extention: https://github.com/FirebaseExtended/experimental-extensions/blob/next/firestore-auth-claims/README.md
Custom Cloud Function: https://github.com/digimbyte/FirebaseDevelopers/blob/master/Cloud%20Functions/Firestore%20Admin%20Manager.md

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.

Change Firebase password in Ionic without Authenticating

I want to change a user's password without authenticating the user, and I want to do this with Ionic.
Currently, this is what I have:
const user = firebase.auth().currentUser;
const credentials = firebase.auth.EmailAuthProvider.credential(user.email,
this._password);
user.reauthenticateWithCredential(credentials)
The problem is that I want to change the user's password without authenticating the user, and that's something I cannot do with firebase.auth().currentUser . The latter won't work if we are dealing with multiple users.
Firebase provides only one way to reset the password without authorization i.e. reset password by email.
You can send a password reset email to a user with the
sendPasswordResetEmail method. For example:
var auth = firebase.auth();
var emailAddress = "user#example.com";
auth.sendPasswordResetEmail(emailAddress).then(function() {
// Email sent.
}).catch(function(error) {
// An error happened.
});
You can also customize email template format. Hope this helps.
It is not possible in the client-side JavaScript SDK to change a user's password without authenticating that user. If it existed it could be called by any malicious user of your app, which would be a pretty massive security risk.
The only way to change a user's password without knowing/specifying their current credentials is through the Firebase Admin SDK. This Admin SDK is made to be run in a trusted environment (such as your development machine, a server you control, or Cloud Functions), and thus can't be abused by users of your app.

Is there a way to determine if a Firebase user's UID is valid?

I am building a server route that I wish to restrict for use only by authenticated users. I plan to send a user.uid with a POST to this route, and I want to validate the UID is one that exists in Firebase. I know I can add UIDs manually in Firebase and check against this data, but is it possible to see what UIDs Firebase authentication is tracking? I think this approach would be better then checking against my own list.
The purpose for this is to ensure that these routes are not accessed with a phony UID (e.g. for malicious purposes).
Validating a UID is not enough to block malicious users: 1) the attackers could pretend to be other users by sending other user's UID, and 2) UID never changes or expires, which means there is no way to enforce the users (or attackers) to re-authenticate.
What you need is to pass the Firebase token from client app to your server, and validate the token before accepting it.
The token is securely signed by Firebase private key. No other party can issue a valid Firebase token.
The token is valid for only one hour. Firebase server will check the account status (e.g. password change event) before issuing a new token.
The token payload contains UID and audience. You should verify audience is your own application.
You can use Firebase Admin SDK or third party libraries to verify a Firebase token. See Firebase doc for details.
You can check whether a specific UID corresponds to a Firebase Authentication user in your project by using the Firebase Admin SDK on your server. From the Firebase documentation on retrieving user data:
admin.auth().getUser(uid)
.then(function(userRecord) {
// See the UserRecord reference doc for the contents of userRecord.
console.log("Successfully fetched user data:", userRecord.toJSON());
})
.catch(function(error) {
console.log("Error fetching user data:", error);
});
A UID is part of the payload when a Firebase user is authenticated to Firebase and is null when a user is not authenticated. You can get the UID upon user authentication. The syntax is different depending on what framework you are working in.
Check out the Firebase API Reference for specific syntax and examples: https://firebase.google.com/docs/reference/
create a token in your client app
private String getAuthTokenAndPost(){
mAuth.getCurrentUser().getIdToken(false).addOnCompleteListener(new OnCompleteListener<GetTokenResult>() {
#Override
public void onComplete(#NonNull Task<GetTokenResult> task) {
if(task.isSuccessful()){
String idToken = task.getResult().getToken();
sendReqToServer(idToken);
}else{
Toast.makeText(CartActivity.this, "couldn't generate Token", Toast.LENGTH_SHORT).show();
}
}
});
return "";
}
then use firebase admin SDK on your server side, here is an example of a Node server
getAuth()
.verifyIdToken(idToken)
.then((decodedToken) => {
const uid = decodedToken.uid;
// ...
})
.catch((error) => {
// Handle error
});
ID token verification requires a project ID. The Firebase Admin SDK attempts to obtain a project ID via one of the following methods:
If the SDK was initialized with an explicit projectId app option, the SDK uses the value of that option.
If the SDK was initialized with service account credentials, the SDK uses the project_id field of the service account JSON object.
If the GOOGLE_CLOUD_PROJECT environment variable is set, the SDK uses its value as the project ID. This environment variable is available for code running on Google infrastructure such as App Engine and Compute Engine.
check the document

Resources