Building a performant real-time web app with Ember Fastboot and Phoenix (Part 4)

Logging in to our API & ember-simple-auth

This article is part 4 in a series. You should first read the previous parts leading up to this, or it may not make much sense!
Part 1: The meeting of two well-aligned, opinionated frameworks
Part 2: Users and Authentication (API)
Part 2.1: Some small adjustments to your API
Part 3: Users and Authentication (UI)
Part 4: Logging in to our API & ember-simple-auth
Part 5: Building a CRUD resource
Part 6: Animating your UI with liquid-fire
Part 7: Room UI & Messages

Congrats on getting this far. Give yourself a high five 🙌! We are now going to get authentication working. You’ll want to create a user account in your dev environment using your newly-working authentication stuff.

Begin by installing another addon (will require stopping and then restarting ember-cli)

ember install ember-simple-auth@simplabs/ember-simple-auth#fastboot

A little about this addon:

  • ember-simple-auth is a very lightweight library for authentication and authorization. Make sure to take a look at the latest instructions for use with Fastboot.

Session store

By default, ember-simple-auth uses localstorage to keep track of user credentials. This won’t work for us, because localstorage is not passed along with requests, and we need Fastboot to receive our credentials in order to render the app in an authenticated (or not authenticated) state — whatever is appropriate. The good news is that there’s something else that we can use: cookies!

We’ll customize ember-simple-auth’s session store by creating a new file ./app/session-stores/application.js

app/session-stores/application.js

You’ll need to stop ember-cli and restart it to pick up on this change.

Authenticator

Another one of the important objects involved with ember-simple-auth is the authenticator. The authenticator is responsible for the particulars of retrieving a credential from some credential provider (our API). We’ll implement ours by creating a new file ./app/authenticators/peepchat.js

app/authenticators/peepchat.js

You could make your authenticator do just about anything, but in this case we’re getting a lot of functionality for free by building around the OAuth2 password grant standard. We just need to customize the API endpoint a little, which as you can see is not difficult.

Authorizer

The authorizer concept in ember-simple-auth deals with taking the data obtained by an Authenticator, and using it in order to communicate with a secured resource (i.e., our API). We’ll use an Authorizer that comes with ember-simple-auth, and we won’t need to alter it at all.

Create a new file ./app/authorizers/oauth2.js

app/authorizers/oauth2.js

We’ll also want to integrate this authorizer with our ember-data adapter (./app/adapters/application.js), so that it passes credentials along automatically. Import the DataAdapterMixin, mix it into the adapter, and specify our authorizer via the authorizer attribute. Here’s what your adapter should look like now:

app/adapters/application.js

Thinking about Routes

An effective mental model to use for Ember’s routing layer is a hierarchy of routes. A given “path” of routes is active at any given time, and transitions in routing state constitute a pivot within the route tree. For example, let’s say we start with a URL /app

app.index route is active

and then click on a link that takes us to /auth/register, this will entail a pivot on the application route route.

auth.register route is active

In our case, this particular routing hierarchy is useful for another reason. We want to guard particular sections of our app from being accessed in various authentication states.

Here’s what we’re aiming for

  • The app route will check to see if users are logged in, and kick them out to auth.login if they’re not
  • The auth route will check to see if users are logged OUT, and kick them out to app.index if they’re logged in
  • The application route will handle our logout action, because we may have more than one section of the app that deals with authenticated users
  • the login route will handle our doLogin action (it already does)
  • the register route will handle our doRegister action (it already does)
  • visiting the top-level domain where our app is hosted should result in a choice for the user to either register or start chatting
  • once the user logs in, they should find themselves at a placeholder screen, identifying the current user (using the /user/current API endpoint we built in part 2 of this series) and giving the user an opportunity to logout

Let’s start by generating a few of the missing route objects for ourselves. It’s important to understand that some of these routes already existed (i.e., the application route always exists), but were not explicitly defined within the source code for our app. In the even that Ember‘s resolver doesn’t find a specific javascript module representing an object like a route, the framework will use an instance of a basic object instead.

Head on over to your terminal and run

ember g route application

This should result in a few new files being created. First head over to ./app/routes/application.js and mixin ember-simple-auth’s ApplicationRouteMixin. This mixin already injects the session service (also an ember-simple-auth concept) onto the route, so we can quickly and easily implement the logout action as well.

app/routes/application.js

You’ll need to make a small update to this route’s unit test as well, since we have a dependency on the existence of the session service. Just add the session service to the “needs” array, to bring that object into the container when this route is tested.

tests/unit/routes/application-test.js

Next, let’s set up the basics for our “logged in” experience. Generate two routes using ember-cli

ember g route app

The app route should just check to see if the user is authenticated, and if they’re not, kick them out to the auth.login route (the login screen). Implement this in ./app/routes/app.js

app/routes/app.js

Let’s generate an auth route (this is one of those that already existed implicitly behind the scenes) with ember-cli

ember g route auth

and implement some similar logic as we did in app — just flipping the condition, and using app.index as the route to send users to in the event that they’re already authenticated.

app/routes/auth.js

Home

We want people who arrive to this app via the top-level to have a clue as to how to proceed, and in the ember world, this means the index route. Generate an explicit index route by running

ember g route index

and open up app/templates/index.hbs

app/templates/index.hbs

When you start up your app and visit http://localhost:4200 it should look something like this:

Visiting the root domain of our app

The logged in experience

Finally, let’s do something with the page users land on once they finish successfully logging in. Create a component called user-info

ember g component user-info

This is going to be a very simple component, that allows us to render information about the currently logged in user, in a very flexible way.

app/components/user-info.js
app/templates/components/user-info.hbs

There’s no property currentUser on the session service yet, but we’ll put it there in a moment.

Generate an index child route of the app route (remember, the app route is the top-most parent of our logged-in experience)

ember g route app/index

Open up ./app/templates/app/index.hbs and set it up to render a card component, welcoming the user and showing their email address

app/templates/app/index.hbs

Think of the “block” (inside part, between the open and close tag) of the user-info component as a callback function. Because we’ve passed an argument to the {{yield}} helper within this component’s template, the “block” as being called with this argument. This allows us to make private component data available to the outside world in controlled way — a very important part of good component composability patterns.

Configuring ember-simple-auth

Open up your environment configuration (./config/environment.js) and add a new section for ember-simple-auth in your ENV declaration

‘ember-simple-auth’: {
authenticationRoute: ‘auth.login’,
routeIfAlreadyAuthenticated: ‘app.index’,
routeAfterAuthentication: ‘app.index’
}

In completeness, it should now look like this

We’ve got two things left, and then we’re all set with ember-simple-auth

Logging in

Open up your ./app/routes/auth/login.js route so we can update the doLogin action. What we want to do is use the session service to obtain and store authentication credentials, based on the username and password that have been entered at the time the action is fired. Here’s what I ended up with:

app/routes/auth/login.js

What about session.currentUser?

In our logged-in experience, our user-info component depends on the existence of a currentUser property on the session service. We need to ensure that whenever the user gets to this app.index route — or probably more generally, the app route and any child route, this property is available for use.

A good place to put this would be the app route’s afterModel hook. The model hook would work as well, but I don’t necessarily want to make this data available in templates (and that’s really what the model hook is for!).

Note that we’ll be making an asynchronous request, but let’s avoid using jQuery due to it not being available in Fastboot for performance reasons. We’ll use a fetch polyfill instead. Go to your terminal and install the ember-network addon

ember install ember-network

Here’s my new app route (./app/routes/app.js)

app/routes/app.js

You’ll have to make one tiny little change to your API. Guardian (on the phoenix side) will need you to specify the “Realm” of your authentication token. In our case, this is “Bearer”. Make this small change in your ./web/router.ex and you should be good to go.

(API) web/router.ex

Now is a great time to open up a PR and merge your work into master once your tests pass. If you’d like to compare your code to mine, here’s my merge commit for all the code for Part 4, so far

And the commits for my small fixes, having to do with the “Bearer” token realm

Toast

Delicious global notifications

Material design calls messages that pop up from the bottom of the screen “toasts”

Although our login, registration and logout all work, we don’t yet do a great job giving feedback to users about success and/or failure. To fix this, we’ll make use of another great ember addon by Lauren Elizabeth Tan called ember-cli-flash, and we’ll wrap it with more Materialize style goodness. Install it using ember-cli, per usual

ember install ember-cli-flash

And while you’re at it, create two new components

  • x-toasts for the notification center
  • x-toast for an individual notification

Put the x-toasts component in your app/templates/application.hbs

app/templates/application.hbs

We need to do one interesting thing in the x-toasts component: reverse the order of the array of messages. This is because, while ember-cli-flash assumes the newest message should be on the top of the queue, we want it to be on the bottom of the queue (since they come up from the bottom)

app/components/x-toasts.js
app/templates/components/x-toasts.hbs

The toast component is a little more interesting for two reasons

  • we want to prevent memory leaks, so we need to ensure that we clean up data associated with the flash message if the component is destroyed
  • We need to add a class to the component very shortly after it is inserted into the DOM, so we can use a CSS animation as the component spawns
  • The “exiting” property is already built in to ember-cli-flash, we just need to bind it to a class via classNameBindings.
app/components/x-toast.js
app/templates/components/x-toast.hbs

You’ll also want to add some styles to your ./app/styles/app.scss to make the messages look pretty

app/styles/app.scss

And customize some ember-cli-flash configuration options so that, by default:

  • the “exiting” property is added to each flash message 5000ms after it is spawned
  • 375ms later the flash message is removed from the DOM

To set this up, add the following to your ENV declaration in ./config/environment.js

flashMessageDefaults: {
timeout: 3000,
extendedTimeout: 375
}

The last thing we need to do is actually use these messages for our three user interactions: register, login and logout

Register

Using ember-cli-flash is quite easy. Basically you inject the flashMessages service onto an object, and then call a method on it in order to generate a message. Everything else is taken care of by the addon

For example, in our application route..

app/routes/application.js

Typically we’d want to handle error scenarios, but in this case there’s not much that can go wrong since we’re erasing a cookie that ember-simple-auth created for us.

Login

However, there are plenty of things that can go wrong with a login. Here are the outcomes I’d like to focus on and handle

  • Successful login
  • Attempted login, but was not successful
  • All other API errors

Basically, it would be good to have a specific error in the event that our API returns a 401 (Unauthorized) error, and a generic error for everything else. Here’s what I ended up with in my auth.login route

app/routes/auth/login.js

Notice that the check I’m making for 401 errors takes into account that the API may be returning more than one error message — I’m checking for any error that has a 401 status code in the array.

Register

Registration can go wrong as well, but we’ll just handle generic success and failure cases. Head over to your ./app/routes/auth/register.js, inject the flashMessages service, and use it in handlers for success and failure scenarios of the save() operation.

app/routes/auth/register.js

Great job! We’re done with notifications for now. Start your app up, and try some login and registration attempts. You should see messages popping up on the screen: red for errors, dark grey for others.

Material design “toasts”, implemented with ember-cli-flash

Try resizing your screen, the notifications will be placed differently for phone, tablet, and desktop browser sized screens

Material design “toast” notifications, optimized for phone-sized viewports

Now is a great time to make a git commit, open up a PR, and merge into your master branch when your tests pass. If you want to compare to my code changes for this second piece of Part 4, here is my merge commit

In part 5, we’ll start adding some real-time functionality, going back to the API side to play with Phoenix Channels!

One clap, two clap, three clap, forty?

By clapping more or less, you can signal to us which stories really stand out.