User Groups, Auth Claims, Custom Tokens and Token Verification

3,380 views
Skip to first unread message

Ryan Mills

unread,
May 30, 2016, 9:55:21 PM5/30/16
to Firebase Google Group
Hi,

I need a group assigned to a user. 

Data Security Rule:

{
 
"rules": {
   
"documents": {
     
"$group_id": {
       
// anyone in this group can write to these documents
       
".write": "$group_id === auth.groupId"
     
}
   
}
 
}
}

Storage Security Rule:

 match /{groupId}/{imageId} {
        allow read
: if resource.metadata.owner. == request.auth.token.groupId;
        allow write
: if request.auth.token.groupId == groupId;
     
}

The only way to add claims to a token is to provide them via firebase.auth().createCustomToken(uid', {groupId: groupId});

With this in mind, how do I authenticate the user? My thinking is:

1. Create the user on the client using firebase.auth().createUserWithEmailAndPassword(email, password) or sign in on the client using firebase.auth().signInWithEmailAndPassword(email, password)
2. Once authenticated, get the user's ID token on the client using firebase.app().auth().currentUser.getToken()
3. Send the user's ID token to the server
4. On the server use firebase.auth().verifyIdToken(token) to verify the user
5. On the server create a new token for the user via firebase.auth().createCustomToken(uid, {groupId: groupId}); and return the results to the client
6. On the client logout and then login using firebase.auth().signInWithCustomToken(token) with the token returned from the server

I can't workout two things though:
1. Do I have to sign-in on the client to auth and then logout and then back in with the new custom token? How else do I authenticate their password is correct/Social login?
2. What am I using for var token in verifyIdToken? firebase.app().auth().currentUser.getToken() returns an object, what do I send to the server and how do I decode this correctly? I have tried JSON.stringify up to the server and then JSON.parse to verifyIdToken but this doesn't work. Can you provide an example of what format/content the token should be in as a result of firebase.app().auth().currentUser.getToken()?
3. Where do I store the groupId against the user in the database? firebase.auth().currentUser.updateProfile() won't save "groupId" against the user in the database. How do I lookup what group the user belongs to when I can't seem to save any metadata against the user in Firebase?

Maybe I'm missing something here, but it seems really difficult to use data security and storage security with groups.

Thanks,

Ryan

Kato Richardson

unread,
Jun 3, 2016, 12:08:21 PM6/3/16
to Firebase Google Group

Hi Ryan,

Thanks for reaching out. Some wonderful challenges here. Sorry it’s taken a while to get back to you. We’ve had quite a bit of internal discussion about this post. The rest of this copy is me channeling various discussions; none of this is my own wisdom. Thanks Mike M., Alfonso, Tristan, and others!

This is a hard problem we don’t have a great answer for yet. There are definitely some places we could improve. Two of the ideas being discussed and considered for future dev work are cross-feature security rules between the Database and Storage, and some notion of groups in our Auth process.

Now on to your numbered questions.

1. Yes, the proposal is correct. This is the best way to do this now.

2. The correct API call is firebase.app().auth().currentUser.getToken(), but it returns a promise which itself return the token, and our suspicion is that you may be serializing that.

This should work:

firebase.app()
    .auth()
    .currentUser
    .getToken(true) // True always fetches a fresh token, recommended.
    .then(function(token) {
        // Send 'token' (string) to backend directly
    }).catch(function(error) {
       // console.log(error)
    });
3. Until we have a better answer, you will need to keep your own mapping between uid <-> groups in the database, and read it from your backend after the sign-in operation. You’ll also want to write to it after a sign-up.

I hope that helps!

☼, Kato


--
You received this message because you are subscribed to the Google Groups "Firebase Google Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email to firebase-tal...@googlegroups.com.
To post to this group, send email to fireba...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/firebase-talk/a169e98e-fabf-40da-9ca8-8fc3f4bfa5ad%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--

Kato Richardson | Developer Programs Eng | kato...@google.com | 775-235-8398

Message has been deleted

Michael Lehenbauer

unread,
Jun 3, 2016, 3:41:05 PM6/3/16
to Firebase Google Group
It does not apply to this case.  Using the normal firebase.auth() APIs authenticates you using Firebase tokens and all security rules are applied as appropriate.

For your original question, with the Database you would normally associate the user to a group via data in your Database rather than encoding it in the token (e.g. see https://stackoverflow.com/questions/14491496/granting-access-to-firebase-locations-to-a-group-of-users/14492117 as an example).  Would that meet your needs?

If you really need the group information in the token (perhaps so it can be accessed in the Firebase Storage security rules), then you might indeed need to log in using password / social auth and then log out / back in using a custom token.

The object returned by firebase.app().auth().currentUser.getToken() should return a Promise that gives you a string (https://firebase.google.com/docs/reference/js/firebase.User#getToken).  And that string is what you'd provide to verifyIdToken.  Is that not what you're seeing?



On Fri, Jun 3, 2016 at 9:29 AM, Sudharsan R <sudhar...@gmail.com> wrote:
I understand from Chris Raynor of Firebase team, that the security rules of the DB are disregarded if Oauth2 tokens used. Does this apply to this use case? Please clarify.

Source: Chirs reply to my question.

Ryan Mills

unread,
Jun 4, 2016, 2:23:49 AM6/4/16
to Firebase Google Group
Thanks Kato and Michael,

Thanks for your help. The solution certainly is convoluted, but I have managed to get it working now and it works for both Database and Storage security rules. Here are my notes to share. 

I hope this helps anyone else out there searching for ways to implement group-based security rules in their app (please add comments to this thread to let the guys know this needs more attention/documentation by Firebase)

How-To:
  1. To use Groups for Database/Storage security rules you need to attach Claims to Custom Tokens.
  2. To mint Custom Tokens you need to use firebase.auth().createCustomToken(uid, {groupId: groupId,custom: true}) on a NodeJS server
  3. To authenticate a user using email/social you need to login as normal on the client using firebase.auth().signInWithEmailAndPassword(email, password) or firebase.auth().signInWithRedirect(provider)
  4. After the client has logged in, get the user's token and send it to the NodeJS server to mint a Custom Token which includes the claims - I was able to achieve this via firebase.auth().signInWithEmailAndPassword(email, password).then(function(user){var token = user.Vc;}); and sending an AJAX POST to the NodeJS server with user.Vc in the header X-Authorization as a string via headers: {'X-Authorization': user.Vc} -- Don't worry about encoding/decoding the token, keeping it in a string in the header and out of the request body works
  5. Then on the NodeJS server verify the token by getting it from var token = req.headers['x-authorization'] and then firebase.auth().verifyIdToken(token).then(function(decoded){var uid = decoded.sub});
  6. Then on the NodeJS server lookup the groupId associated with the user from '/users/uid' via ref = db.ref('/users/' + uid); and ref.once('value', function(snapshot){var value = snapshot.val(); groupId = value.groupId; ...});
  7. Then on the NodeJS server mint a Custom Token with the groupId in it firebase.auth().createCustomToken(uid, {groupId: groupId,custom: true}) and send it back as the response to the AJAX POST
  8. Then on the client use firebase.auth().signInWithCustomToken(token).then(function(user){}); to now login as the uid with the Custom Token which includes the groupId claim
Questions:
1. Do I need to use firebase.app().auth().currentUser.getToken(true) to get the user id token to submit to the NodeJS server or can I just use firebase.auth().signInWithEmailAndPassword(email, password).then(function(user){var token = user.Vc;}); as above? Is there anything wrong with this approach (Note: user.VC is undocumented, I just found it on the console and thought this was a better way seeing I don't have to make another request for getToken()) - verifyIdToken() seems to like the user.Vc variable and NOT a JSON AJAX POST version of firebase.app().auth().currentUser.getToken(true) - You need to send the token as a string not an encoded object which it returns.

Feedback:
1. Can you please make it so that when a user logins in via email/social, Firebase automatically attaches any claims that exist in /users/uid/claims to the token? This would save all this trouble of logging in, logging out and then logging back in with a custom token.
2. It seems email/social login and custom tokens are mutually exclusive - Can this be integrated somehow.

Thanks again,

Ryan

Michael Lehenbauer

unread,
Jun 6, 2016, 12:30:20 PM6/6/16
to Firebase Google Group
Hey Ryan,

Glad you got it working!  And thanks for summarizing everything for those that follow in your footsteps.

As for your question, you should not use user.Vc.  Vc is an internal variable that's automatically named by the compiler and will almost certainly change in future versions.  You'll need to use the public APIs I'm afraid.  I didn't understand your comment about "NOT a JSON AJAX POST version of firebase.app().auth().currentUser.getToken(true) - You need to send the token as a string not an encoded object which it returns."  Using a code snippet similar to Kato's, token should be a string.  Is that not what you're seeing?

Thanks for the feedback.  We agree that this is pretty painful and we'll see what we can do to make Storage / Database / Auth / Rules play together better.

Thanks,
-Michael

Ryan Mills

unread,
Jun 6, 2016, 10:52:11 PM6/6/16
to Firebase Google Group
Hi Michael,

Thanks again. No probs, won't use user.Vc. Thanks for clarifying.

RE JSON, when I use $.ajax and push up data: { token: token }, JSON.stringify encodes the object and not the string. This is what was causing me a headache because the server only knows how to decode the token as a string and not an object.

firebase.app()
.auth()
 
.currentUser
 
.getToken(true) // True always fetches a fresh token, recommended.
 
.then(function(token) {
   
// Send 'token' (string) to backend directly

   console
.info(token); // token is a string
   JSON
.stringify(token); // token is an object
 
}).catch(function(error) {
   
// console.log(error)
 
});

Also - What does getToken(true) do? Does the true introduce a round trip to the Firebase server to refresh/get a token?

Ryan

Michael Lehenbauer

unread,
Jun 7, 2016, 12:23:49 PM6/7/16
to Firebase Google Group
Yeah, the "true" will always incur a roundtrip to get a "refreshed" token, whereas getToken(false) normally will return a cached token which could be expired or be about to expire.  You may be able to drop the true, especially if you're doing this immediately after signin (which I believe will have gotten a fresh token already).

-Michael

Jeremy

unread,
Aug 1, 2016, 12:13:40 PM8/1/16
to Firebase Google Group
I am very disappointed after reading through this discussion.  I have a moderators location in the database that contains all of the uids for those who are moderations and can delete/edit anything.  On each post that authenticated users add, I store an author key with their uid.  In the DB rules I can check to see if the currently authenticated user is the owner of the Post or is childOf('moderators').  Works perfectly.  I wish we could do the same with storage.  My whole purpose in moving to firebase is to not have to run a server, but it is looking as through this is not going to be the case.

Kato mentioned the possibility of cross-feature security between Database and Storage (which would solve the problem here).  Any news on whether this will be come a reality and timeframe for doing so?

Jeremy

Ziggy Crane

unread,
Sep 6, 2016, 11:06:14 AM9/6/16
to Firebase Google Group
What's the status with this one? Have you guys decided how you will solve this? This is a pretty important issue that needs to be solved. Creating custom token with claims does not really work for me. 

Cross-feature security rules seems the logical solution. Are you guys implementing them already? 

Mike Mcdonald

unread,
Sep 6, 2016, 11:32:02 AM9/6/16
to Firebase Google Group
Ziggy,

Totally agree that it's a huge issue that needs to be solved, and it's one that we're actively investigating. As previously mentioned, building this in a naïve way (fetching data every time) would not scale from a tech perspective nor a cost perspective--it might present a decent user experience when writing rules, but things would quickly spiral out of control in production.

We think we have a reasonable strategy for caching rule evaluations and making cross service rules scale (both in terms of cost and tech), but it'll take a little time to implement well. Fear not, it's definitely high on our priority list :)

Thanks,
--Mike

Gartorware Corp

unread,
Sep 7, 2016, 10:33:53 AM9/7/16
to Firebase Google Group
+1 to cross-feature security rules, this would be simply amazing since we could set permissions painless based on database data. Furthermore, I think one of the most cool features of firebase is the possibility to not have a server at all!

Thank you very much!

Ryan Mills

unread,
Sep 7, 2016, 9:35:40 PM9/7/16
to fireba...@googlegroups.com
+1 on database-based cross-feature security rules -- Minting tokens requires a server and is painful, especially when they never expire!

--
You received this message because you are subscribed to a topic in the Google Groups "Firebase Google Group" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/firebase-talk/77i9CRlwg88/unsubscribe.
To unsubscribe from this group and all its topics, send an email to firebase-talk+unsubscribe@googlegroups.com.

To post to this group, send email to fireba...@googlegroups.com.

Al Hounsell

unread,
Sep 21, 2016, 10:22:57 AM9/21/16
to Firebase Google Group
+1 - I really need this
To unsubscribe from this group and all its topics, send an email to firebase-tal...@googlegroups.com.

To post to this group, send email to fireba...@googlegroups.com.

Christoffer Buusmann

unread,
Oct 21, 2016, 2:17:24 PM10/21/16
to Firebase Google Group
Is there any update on the progress of this Kato?

Thanks
Chris

John Blair

unread,
Nov 24, 2016, 5:57:13 PM11/24/16
to Firebase Google Group
Hi Kato,

I've just come across this thread after hitting the exact same problem.
Apologies for adding another +1 to the thread, but I didn't want to create another duplicate.
Is there any progress on the cross feature security rules?

Thanks

John


On Friday, 3 June 2016 17:08:21 UTC+1, Kato Richardson wrote:

Robert Nordon

unread,
Nov 29, 2016, 7:46:26 PM11/29/16
to Firebase Google Group
+1

Nelly Garcia

unread,
Dec 21, 2016, 10:27:27 AM12/21/16
to Firebase Google Group
+1 Would be a great feature to avoid node.js backend.

Simon Bell

unread,
Jan 21, 2017, 10:27:05 AM1/21/17
to Firebase Google Group
+1 for cross-feature security rules, especially as I would like to use Auth0 to handle authentication and thus it would be tricky to add groupId(s) to the JWT payload. Being able to query the Realtime Database within the Storage rules would be incredibly useful.

Mike McDonald

unread,
Jan 24, 2017, 8:07:45 PM1/24/17
to Firebase Google Group
Actually, Auth0 makes it *crazy* easy to add additional info to a payload. Their rules basically let you execute an arbitrary JS function (which I believe can call out to external services) before token creation, which can, for instance, add additional roles to a token (as would be necessary for group membership).

Curious, do you already use Auth0 and don't want to switch to Firebase Auth, or is Firebase Auth lacking features (besides the obvious one above ;)? What would make you switch?

Thanks,
--Mike

Hugh A

unread,
Apr 15, 2017, 1:46:16 AM4/15/17
to Firebase Google Group
I've been working on a similar problem and agree would be a great feature. For those not wanting to set up there own back end, the route I've taken is to setup a cloud function with a service account that is able to verify and then mint new tokens with the group details. I haven't yet tested it under any load, but it seems to work quite well.

My latest problem though is updating the token if/when a user's group details change (in this case they can join a number of groups, and need to wait for approval). I'm currently leaning towards having a "child_[added|removed]" listener to user's group list in the database, then updating the token any time children are added/removed.

Ari Kurniawan

unread,
Jun 4, 2020, 10:31:31 AM6/4/20
to Firebase Google Group
Reply all
Reply to author
Forward
0 new messages