In our application we use Firebase's custom login functionality to store some metadata in user's auth token.
Later we send this token to one of our web applications to perform a task on behalf of the user using a new admin token to disable security rules. The idea is that a particular location is not writable by authenticated users directly, but data could be written in that location after some server side calculations and validations are done.
Here's a sample code of what I'm trying to do:
var testRef = new Firebase(firebaseApp + 'test');
testRef.auth(userFirebaseAuthToken, function(error, result) {
if (!error) {
var userId = result.auth.userId;
// perform validations and calculations
var tokenGenerator = new FirebaseTokenGenerator(firebaseSecret);
var token = tokenGenerator.createToken({admin: true});
var protectedRef = new Firebase(firebaseApp + '/protected/');
protectedRef.auth(token, function(error) {
if (!error) {
protectedRef.child('foo').push({id: userId});
}
});
}
});
But I get this error:
FIREBASE WARNING: set at /protected/foo/-Ityb1F6_G9ZrGCvMtX- failed: permission_denied
When desired behavior is to be able to write in that location using an admin token.
I understand that this might not be a Firebase issue, but some JavaScript good/bad parts, but what I need to do is to write in some protected location on behalf of a user which even though is not authorized to write in that location, but needs to be authenticated.
Based on what I've seen from my test units and from experience, I don't think that new Firebase actually gives you an independent connection to the data. That is to say, these are both connected to the same Firebase instance internally (I think):
var refA = new Firebase('...');
var refB = new Firebase('...');
So if you want to re-auth, I'm pretty sure you need to call unauth first, which will probably affect your testRef instance as well.
If you truly need to have multiple instances opened to the database with different auth at the same time, then you'll have to look at node-fibers or some other worker pool model, which will allow separate connections.
However, give some thought to this; you are probably overthinking your approach. If you are writing on behalf of a user who doesn't have permissions, then you probably don't actually need to be authenticated as that user.
I've written an entire app with secure Firebase components that are consumed by a third-party app, and then written back to privileged paths and then read by users, and haven't yet run into a condition where the server would need to demote its permissions to do this.
That's not meant to presume I know your use case, just to give you some encouragement to keep things simple, because trying to juggle authentication will not be simple.
My approach is to treat the Firebase security rules as a last defense--like my firewall--rather than part of the programming algorithm used by privileged processes.
Related
Before adding a new user to Firebase Authentication should the name be qualified first:
The name must not be null
The name must not be empty
The name must contain one D character at least
Examples:
"Frank van Puffelen" => It is unacceptable because there is no D character
"Doug Stevenson" => It is acceptable
"Alex Mamo" => It is unacceptable because there is no D character
"Renaud Tarnec" => It is acceptable
"" => It is unacceptable because it is empty value
NULL => It is unacceptable because it is a null value
On the client side before adding a new user I check if the name follows the above qualifiers or not but the problem is if someone modifies the code.
The client side is not safe and I should check again on the server side if the name follows the rules or not.
So the question is why there is no Rules tab inside Firebase Authentication?
Since you want to check that the user name (the displayName I guess) follows the set of constraints listed at the top of your question you can take advantage of the new blocking Cloud Functions that "let you execute custom code that modifies the result of a user signing in to your app".
For example:
exports.checkDisplayName = functions.auth.user().beforeCreate((user, context) => {
if (!user.displayName || !user.displayName.toUpperCase().includes('D')) {
throw new functions.auth.HttpsError(
'invalid-argument', `displayName is invalid`); // adapt as follows
}
});
More details in the specific section of the doc, and in particular on how to catch and handle the error in your front-end.
The security rules concept is used to prevent unauthorized access to your Firebase resources such as database and storage. The displayName property is optional irrespective of which authentication method you chose.
If you require users to have a displayName then you can:
Check if user has displayName set every time they login. If not, then redirect them to a screen where they can set a name.
Disable sign-ups directly from Firebase client SDKs and use Firebase Cloud Functions with the Admin SDK to create user. No one else can reverse engineer the functions code so the validation on server side will ensure a user has displayName.
exports.createUser = functions.https.onCall((data, context) => {
const { displayName, email, password } = data;
// check if displayName is valid
// if not return error
// create user using Admin SDK if all data provided is valid
return { message: "User created" };
});
Then you can login your user with the Client SDK using signInWithEmailAndPassword()
In case you are using any Auth providers e.g. Google, Facebook and the display name is unavailable for some reason, then you'll need some custom logic as explain in method 1 above.
Either of the solution does not prevent users from using updateProfile() APIs so make sure have some validation on client end as well and report such events somewhere in the database where you can monitor it.
I just started learning node, express, and Firebase and after digging around, I've decided to ditch express's express-session API and go with Firebase's authentication system.
I'm trying to build a simple app that can handle multiple user sign-ins with express but I'm lost on where and when to use Firebase functions. I know I need some sort of session on the client side, but I'm unsure how to implement it.
Below is what I want my app to do:
Log in with user credentials
Store user information in a session object
Redirect to the dashboard
Retrieve user details from session object
Here is what I have so far:
app.post('/login', (req, res, next) => {
firebase.auth().setPersistence(firebase.auth.Auth.Persistence.LOCAL)
.then(function() {
firebase.auth().signInWithEmailAndPassword(req.body.email, req.body.password).then((user) => {
res.redirect('/dashboard');
})
.catch((err) => {
res.send(err);
});
})
.catch(function(error) {
// Handle Errors here.
var errorCode = error.code;
var errorMessage = error.message;
console.log(errorMessage);
});
});
I've read up on Admin SDKs, authChange, tokens and client SDKs. I'm a total newbie at this and I'm blown away by all the information. I feel like I'm missing an onAuthChange statement, but I'm unsure where to put it. This is also a testing nightmare because my local server returns an error when I use persistence.
How can I use session-like objects in Express? What do I need to implement to make sure multiple users can use my app at the same time?
I found my answer. There's no need to initiate sessions in the back end because Firebase functions create a session object in LocalStorage. Powerful stuff.
So, maybe I missed this somewhere in the docs but I couldn't find anything of the sort.
I wan't my users to have to type in their current password to be able to create a new one. From what I understand if the user is authenticated he is able to update his password without providing his current one.
Even if this might be somewhat secure I would rather have him type his old one to prevent people from going on already authenticated sessions from say family members or so and changing the pw.
Is there any way to do this?
(I have no problem using the Admin SDK since I already set up a server for these kind of things)
UPDATE: (Use - reauthenticateWithCredential)
var user = firebaseApp.auth().currentUser;
var credential = firebase.auth.EmailAuthProvider.credential(
firebase.auth().currentUser.email,
providedPassword
);
// Prompt the user to re-provide their sign-in credentials
user.reauthenticateWithCredential(credential).then(function() {
// User re-authenticated.
}).catch(function(error) {
// An error happened.
});
PREVIOUS VERSION
you can use reauthenticate API to do so. I am assuming you want to verify a current user's password before allowing the user to update it. So in web you do something like the following:
reauthenticateAndRetrieveDataWithCredential- DEPRECATED
firebase.auth().currentUser.reauthenticateAndRetrieveDataWithCredential(
firebase.auth.EmailAuthProvider.credential(
firebase.auth().currentUser.email,
providedPassword
)
);
If this succeeds, then you can call
firebase.auth().currentUser.updatePassword(newPassword);
I've recently built an app using Firebase as the data store and secured it using the security rules that only the user can read and edit their data which all works fine.
But now I want to build an admin section to list users and update details if need be, but the problem I'm running into is the fact that I cant access their data as I'm not the user. I'm seeing if its possible to allow read or write permissions to the user or admin?
UPDATE
Token generation
var tokenGenerator = new FirebaseTokenGenerator(authSecret);
var token = tokenGenerator.createToken({admin: true});
Security rule
".read": "auth.admin == true || otherauthmthod"
The method that you described above will work, assuming you update your security rules to look for the auth.admin bit. Alternatively, and likely a bit easier, is to generate an admin token, which will allow you to skip the execution of security rules entirely. This can be accomplished via:
var token = tokenGenerator.createToken({ any: "data" }, { admin: true });
See https://github.com/firebase/firebase-token-generator-node for more details.
How can check, on server side route, if user is logged?
I would add check on 'before', but Metor.user() don't work here.
thanks in advance.
p.s. I have found How to get Meteor.user() to return on the server side?, but not work on iron-router
I'm afraid that this is not possible. I guess that the problem comes from the fact that you're trying to connect to the server with two different protocols - both literally and in logically - so there is no obvious way to relate this two actions.
There is, however, a pretty simple solution that may suit your needs. You'll need to develop a simple system of privileges tokens, or secret keys, or whatever you call them. First, create a server method
var Secrets = new Meteor.Collection("secrets"); // only on server!!!
Meteor.methods({
getSecretKey: function () {
if (!this.userId)
// check if the user has privileges
throw Meteor.Error(403);
return Secrets.insert({_id: Random.id(), user: this.userId});
},
});
Then, you can now use it on the client to get the secretKey which attach to your AJAX request (or something), either within the HTTP header or in the URL itself. Fear not!
They will all be encrypted if you're using HTTPS.
On the server side you can now retrieve the secretKey from the incoming request and check if it is present in the Secrets collection. You'll know then if the user is granted certain privileges or not.
Also you may want to remove your secret keys from the collection after some time for safety reasons.
If what you're looking to do is to authenticate the Meteor.user making the request, I'm currently doing this within the context of IronRouter.route(). The request must be made with a valid user ID and auth token in the header. I call this function from within Router.route(), which then gives me access to this.user:
###
Verify the request is being made by an actively logged in user
#context: IronRouter.Router.route()
###
authenticate = ->
# Get the auth info from header
userId = this.request.headers['x-user-id']
loginToken = this.request.headers['x-auth-token']
# Get the user from the database
if userId and loginToken
user = Meteor.users.findOne {'_id': userId, 'services.resume.loginTokens.token': loginToken}
# Return an error if the login token does not match any belonging to the user
if not user
respond.call this, {success: false, message: "You must be logged in to do this."}, 401
# Attach the user to the context so they can be accessed at this.user within route
this.user = user
###
Respond to an HTTP request
#context: IronRouter.Router.route()
###
respond = (body, statusCode=200, headers={'Content-Type':'text/json'}) ->
this.response.writeHead statusCode, headers
this.response.write(JSON.stringify(body))
this.response.end()
This code was heavily inspired by RestStop and RestStop2. It's part of a meteor package for writing REST APIs in Meteor 0.9.0+ (built on top of Iron Router). You can check out the complete source code here:
https://github.com/krose72205/meteor-restivus