Firebase / Uncategorized

Firebase Database Authentication

March 2, 2017

Tags: , , , ,

Firebase Database Authentication

I’ve recently been working on a project that uses firebase db for sharing state between a VR/Unity app and a web-based app with Auth0 login. It works really well, and the Unity side is easy to set up and get started.
However, as much as it’s easy to read and write to the db, it’s also pretty easy to delete data – especially true for a malicious user. To add some protection for this, you can set up authentication for your database and restrict access to specific nodes in the db. As an example, assume that we have a system that allows a super user to create game instances for people to join. The super user will have read and write access to their own user node, and the players will have read/write access to the game node the super user has created. The database structure will be something like


gamedatabase
- users
- superuseridA
- gameidA
- gamestate
- gameidB
- gamestate
- gameidC
- gamestate
- superuseridB
...

These are the steps we’ll be following:
– Enable Service account authentication in Firebase
– Apply Rules in the Firebase console to restrict user access.
– Create a custom token in Auth0 to return to the Unity app for authenticating with Firebase (FirebaseAuth.SignInWithCustomTokenAsync(token)).
– Create an Auth0 rule for adding specific properties to our custom token that are used in the Firebase rules.
– Add the Unity Firebase plugin and read/write to Firebase

Service Account Authentication

  • Open up the firebase console
  • Click on the Settings cog at the top of the left menu and select Project Settings
  • Select the Service Accounts tab
  • Click Generate New Private Key.

This will generate a json download containing the info you need to set up token generation in Auth0. The data looks like this :


{
"type": "service_account",
"project_id": "yourfirebaseid",
"private_key_id": "443e8fe548e17b6549",
"private_key": "-----BEGIN PRIVATE KEY-----[PRIVATE KEY DATA]",
"client_email": "firebase-adminsdk-offzi@yourfirebaseid.iam.gserviceaccount.com",
"client_id": "45264564526245",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://accounts.google.com/o/oauth2/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-offzi%40yourfirebaseid.iam.gserviceaccount.com"
}

Firebase Rules

In the Firebase console, click on Database in the left menu
Select the Rules tab

By default, a user authenticated rule looks like this:


{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid",
}
}
}
}

This means that a user can only read or write to a node under their own user id, which is great, but we want to go a little further and restrict access based on the type of user, as well as the game they’re playing. We want our host user to be able to read and write to their userid node (so they can create and remove games), and we want guests to be able to access the game state created by the host, but nothing else. Note that in this case we don’t care about the guest user’s uid, only the host user.
Our more restrictive rule will look something like this:


{
"rules": {
"users": {
"$uid": {
".read": "$uid === auth.uid && auth.token.usertype === 'host'",
".write": "$uid === auth.uid && auth.token.usertype === 'host'",
"$gameId":
{
".read": "$uid === auth.uid && $gameId === auth.token.gameId",
".write": "$uid === auth.uid && $gameId === auth.token.gameId"
}
}
}
}
}

The auth object gives access to the data in our token (such as user id), and auth.token gives access to any custom properties added to our token (explained further in the next section where we create our custom token).

There’s a handy Simulator (top right of the rules box) if you want to test out the read and write setup works for the data supplied.

Create Custom token

NB. If you are not using Auth0, it’s possible to spin up your own code to create the JWT, as well as with other third party libraries. The following is specific to Auth0.

Firebase Add-On

Enable the Firebase Add-On in Auth0 and fill out the settings data using the data from the json file created here.

Now, when you signin with Auth0, a token will be generated with your user id. You then need to hit the delegation endpoint with this id to obtain the custom auth token for firebase. You can test this in the Auth0 console.


{
"uid": "someuserid",
"iat": 1488466274,
"exp": 1488469874,
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iss": "firebasename@firebaseid.iam.gserviceaccount.com",
"sub": "firebasename@firebaseid.iam.gserviceaccount.com"
}

So we’re half way there – we just need to add a gameId and userType and we’ll have enough data to authenticate our guest user with firebase.

Adding custom data to the token

We now need to add the userType and gameId data to our custom token in a claims object. To do this, we need to create a custom Auth0 rule that will be run as part of our auth0 signin process.

Add the following rule to apply the userType and gameId to the JWT


function (user, context, callback) {
if (context.protocol === 'delegation') {
user.firebase_data = {
gameId: context.request.body.gameId,
usertype: context.request.body.usertype
};
}
callback(null, user, context);
}

With the correct properties passed in, this will generate a JWT like this for the host user:


{
"uid": "superuserId",
"claims": {
"gameId": "gameidA",
"usertype": "host"
},
"iat": 1488466274,
"exp": 1488469874,
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iss": "firebasename@firebaseid.iam.gserviceaccount.com",
"sub": "firebasename@firebaseid.iam.gserviceaccount.com"
}

and this for a guest user


{
"uid": "normaluserId",
"claims": {
"gameId": "gameidA",
"usertype": "guest"
},
"iat": 1488466274,
"exp": 1488469874,
"aud": "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
"iss": "firebasename@firebaseid.iam.gserviceaccount.com",
"sub": "firebasename@firebaseid.iam.gserviceaccount.com"
}

Note that we don’t need the gameid in the host user token, as the host firebase rules don’t require it.

This is a great tool for testing that the current data is contained in generated token.

Now we are correctly generating the guest token, we pass this back to our app and use it to authenticate with Firebase.

Unity

Authenticating the app


var auth = FirebaseAuth.DefaultInstance;
auth.SignInWithCustomTokenAsync(custom_auth0_token).ContinueWith(OnAuthenticationResponse);

Accessing data


OnAuthenticationResponse(Task task)
{
FirebaseUser newUser = task.Result;
var appOptions = new AppOptions();
appOptions.DatabaseUrl = new Uri(databaseUrl);
appOptions.ApiKey = newUser.UserId;
var firebaseApp = FirebaseApp.Create(appOptions);
var database = FirebaseDatabase.GetInstance(firebaseApp);
var sessionPath = string.Format("/users/{0}/{1}", newUser.UserId, gameId);
var sessionRemote = database.GetReference(sessionPath);
}

If we have a host user with id “superuseridA”, and a game with an id “gameidA”, the guest user can only read and write here:

/users/superuseridA/gameidA/

Note – we have assumed that the Unity app knows the game id already (from a game menu or similar).

That’s all there is to it!

***** Edit *****

There are a few extra steps for testing in Editor with auth rules as auth seems to be stubbed unless running on device.

These are the steps to get it running:

  • Open up the firebase console
  • Open the project you want to connect to
  • Click on the cog and select Permissions
  • Click on Service Accounts
  • To the right of the firebase-adminsdk account, click on More Options menu (three dots).
  • Select Create Key.
  • Choose P12 and click Create
  • This will start downloading the certificate. Once the download is complete, add it to your Unity project in the Editor Default Resources folder.
  • When you initialise your Firebase connection, you do so using the following


firebaseApp = FirebaseApp.DefaultInstance;
FirebaseApp.DefaultInstance.SetEditorDatabaseUrl("yourfirebaseurl");
FirebaseApp.DefaultInstance.SetEditorP12FileName("yourcertificate.p12");
FirebaseApp.DefaultInstance.SetEditorServiceAccountEmail("yourserviceaccountemail");
FirebaseApp.DefaultInstance.SetEditorP12Password("notasecret");
database = FirebaseDatabase.DefaultInstance;
sessionPath = string.Format("/users/{0}/sessions/{1}", model.Settings.EditorFirebaseUserId, sessionId);
sessionRemote = database.GetReference(sessionPath);
sessionRemote.GetValueAsync().ContinueWith(OnFirstDataReceived);

That should now connect to your DB in Editor 🙂

0 likes

Author

Your email address will not be published.