Isnor Creative Blog
Ruby on Rails, Ember, Phoenix, Elixir, React

Building an Ember application with Firebase hosting, auth, and database

I recently developed a little Ember application for logging information about my Kombucha brewing obsession hobby. Given that it’s a free aplication that will serve literally just me a very niche market, I thought that Firebase hosting would be a good option, as it’s a free service up to a pretty good limit for a hobby project such as this, and there was nothing required beyond some simple data storage and a basic auth system that would give users acceess to their own data.

First I installed Firebase:

$ ember install emberfire

Then I added firebase configuration to the ENV key in config/environment.js (I’m using dotenv to store sensitive data:

require('dotenv').load();
module.exports = function(environment) {
  var ENV = {
    firebase: {
      apiKey: process.env.FIREBASE_API_KEY,
      authDomain: process.env.FIREBASE_AUTH_DOMAIN,
      databaseURL: process.env.FIREBASE_DATABASE_URL,
      storageBucket: process.env.FIREBASE_STORAGE_BUCKET
    }
  }
etc

Next I added Torii for authentication:

$ ember install torii 

And in config/environment.js I added a torii key to ENV:

var ENV = {
  torii: {
    sessionServiceName: 'session'
  }
}

I added a Torii adapter in app/torii-adapters/application.js:

import ToriiFirebaseAdapter from 'emberfire/torii-adapters/firebase';
export default ToriiFirebaseAdapter.extend();

I chose to use only Google auth, which I turned on in the Firebase control panel.

I couldn’t find a lot in the way of documentation on securing user data. It basically boiled down to nesting user data in Firebase under the user UID and then customizing my Ember Data application adapter to acknowledge that. To accomplish this I first set up my application route to store the logged in user UID in a service at login time in app/application/route.js:

import Ember from 'ember';
export default Ember.Route.extend({
  /* injecting service to set uid */
  journalist: Ember.inject.service('journalist'),
  beforeModel() {
    return this.get('session').fetch().catch();
  },
  actions: {
    signIn(provider) {
      this.get('session').open('firebase', {provider: provider}).then(data => {
        /* setting uid in service for later retrieval elsewhere */
        this.get('journalist').setUID(data.uid);
        this.transitionTo('entries');
      });
    },
    signOut() {
      this.get('session').close().then(() => this.transitionTo('application'));
    }
  }
});

This is the service that is storing the UID for the logged in user in app/services/journalist.js. It makes use of the ember-local-storage addon:

import Ember from 'ember';
import { storageFor } from 'ember-local-storage';
export default Ember.Service.extend({
  uid: null,
  localStore: storageFor('journalist-session'),
  init() {
    const localStore = this.get('localStore');
    this.set('uid', localStore.get('uid'));
  },
  setUID(id) {
    this.set('uid', id);
    this.set('localStore.uid', id);
  }
});

Next I added the “storage” file required by ember-local-storage in app/storages/journalist-session:

import StorageObject from 'ember-local-storage/local/object';
const Storage = StorageObject.extend();
 Storage.reopenClass({
   initialState() {
     return {
       uid: null
     };
   }
 });
export default Storage;

Finallly I added a pathForType function to my Ember Data application adapter in app/application/adapter.js. Here I am injecting the service I created to store the UID, and injecting that into the pathForType path.

import FirebaseAdapter from 'emberfire/adapters/firebase';
import Ember from 'ember';
export default FirebaseAdapter.extend({
    /* inject service to access uid */
  journalist: Ember.inject.service('journalist'),
    /* path for type nests /entries under users/${uid} */
    pathForType(type) {
      let uid =  this.get('journalist.uid');
      let path = Ember.String.pluralize(type);
    return  `users/${uid}/${path}`;
   }
});

I added some rules in my Firebase database settings to limit users to their own data:

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

Handling auth in templates was as simple as

<button class="btn btn-danger"  {{action "signIn" "google"}}>Sign in with Google</button>

and

{{#if session.isAuthenticated}}
  Something for signed in user goes here
{{/if}}

After that, working with Ember Data was standard procedure. Here’s an example where I am finding all entries for the user in entries/route.js. Saving a record follows the same out-of-the-box method as you’d normally use in Ember:

import Ember from 'ember';
export default Ember.Route.extend({
    /* nothing special required for ember data save, findAll, etc, adapter takes care of everything */
    model() {
        return this.store.findAll('entry');
    }
});

Deployment to Firebase hosting is also dead simple:

$ npm install -g firebase-tools
$ ember build
$ firebase init
$ firebase deploy

Getting the Firebase database rules working with Ember was a pain in the ass, only because I couldn’t find documentation about this. But now that I now how it works, it’s dead simple, at least it was in this relatively simple case. I will definitely consider Firebase for future projects where applicable.

Gordon B. Isnor writes about Ruby on Rails, Ember.js, Elixir, Phoenix, React, and web development in general. If you enjoyed this article, join his newsletter.

comments powered by Disqus