Lesson 15

Adding authentication

6

⚠️ This tutorial uses an old version of Ember. Some things might have changed!

So far, our login has been a fake one, where we put in "letme" and "in" as our username and password. In this chapter, we're going to build a real authentication system using an addon called Ember Simple Auth.



How tokens work

If you've written web applications in a language like PHP before, you're probably used to having sessions dictate if your user can access things or not (e.g <?php if ($_SESSION['user']): ?>). This is called Cookie-Based Authentication.


Since Ember (and other single-page application frameworks) exists purely on the frontend though, this concept will not really work. This is where tokens come in.


Token-Based Authentication is a more modern approach and relies on the concept that you send a token which identifies you with every request you make to the server.


If you're a visual guy (like me), this picture might make the process clearer


Some of the main benefits with this solution is that it's stateless (your backend doesn't need to keep a session store) and much more suitable for mobile application (in case you ever want to extend your service beyond the browser).

Installing Ember Simple Auth

First of all, we'll install the Simple Auth addon for Ember CLI:

Remember to restart the Ember server so that your new addon can get activated. Now it's time to edit our files to take advantage of this new authentication system!

Allowing signups

The signup-part doesn't actually need Ember Simple Auth, but it's a good way to show how our Ember frontend and our November backend can work together seamlessly.


Let's start from the beginning and assume that we don't have any users registered yet. We'll begin by adding a signup-action to our signup-button on the index page:

app/index/template.hbs

What we want is to have the user enter their desired credentials, click "Sign up", and then make a POST request to localhost:9000/signup where we'll grab the credentials and add the user to the database.


Let's create the signup action in our controller file. We'll also remove the fake-login action and replace it with just a console.log for now:

app/index/controller.js

Let's go through the changes we just made. As mentioned, the login action is still not working yet, although we're doing a console.log so that we know when our Ember app is attempting to log us in. Notice that we're using the new ES6 Template Strings.


Our signup action is a simple jQuery AJAX call that uses our apiURL-environment variable so that it makes a request to the correct domain.


Alright, to see if our AJAX request gets fired correctly. Type in a username and password, click on the signup-button and bring up the console!


Yup, we're sending a request to localhost:9000/signup!


The Node.js API will return status 404 because obviously we haven't actually created the /signup endpoint in November yet. But apart from that, everything looks good on the front-end side!


Head back to your November project and bring up a terminal window! It's time to create that missing /signup page!

This will create the file app/actions/signup.js. Actions in November are like simplified models. All they do is add a POST route with the specified URL and it's up to you to code how it should behave from there.


Before we go on, we should add a password field to our user model in the backend, because right now, we've only specified a username and an about_me field.


Remember that passwords should always, always, always be encrypted in your database (in case something bad happens). What we're storing is therefore not the plain text password but a hash. We also, don't want it to be fetched by default (it should be admin-only). Let's specify that in our user model:

app/models/user.js

To force Sequelize to update the table structure and add the hash column to the database, you will need to open up the file app/middleware/sequelize.js and specify sync({ force: true }) at the line where the database syncs:

app/middleware/sequelize.js

After this, visit localhost:9000/users so that the changes are made to your database.


Important! {force: true} will always reset your entire database (including the data) and reconstruct it from scratch to make sure that it has the latest structure, so use it carefully!


After the hash column has been added, make sure you remove {force: true} so that your data doesn't keep getting deleted!


Now, back to the signup! We're going to use an NPM library in our November project called bcrypt to encrypt the user's password, so let's download that first:

Now we'll import that library in our file and add some simple logic:

app/actions/signup.js

That's it! Now go back to your Chirper app's signup page. Type in your desired username and password, hit the signup-button and make sure that the console is visible.


The signup seems to have worked!


To make sure that the user was indeed created, you can go into the MySQL CLI:

Awesome, now we have a working signup! Let's move on to the login-part.

Logging in

This is where the Simple Auth addon will save us a massive amount of time! Let's go back to our index controller and start by injecting Simple Auth's session service:

app/index/controller.js

The question we need to answer now is: how do we want to log in? As you probably know, many web apps offer their users multiple ways of logging in (e.g. "Log in with Facebook", "Log in with Twitter"... etc.)


Since we want to keep things simple, we're going to go for a standard "username + password" OAUTH2 login system. In order to support this, we need to create an OAUTH2 authenticator, which will simply send our username and password to a certain API endpoint.


This is very easy to manage with Simple Auth: we simply create a new app/authenticators-folder and inside we'll add all the authenticator types that we want to support (only OAUTH2 for now):

app/authenticators/oauth2.js

As you can see, Simple Auth already has a OAUTH2PasswordGrant library. So we simply import it, and then export a slightly modified version where we specify where to send the request when we log in (localhost:9000/login).


Now that our authenticator is set up, we can replace the console.log() in our login-action with session.authenticate, which takes three parameters: the authenticator, the identification and the password:

app/index/controller.js

Try logging in with the username and password that you signed up with and you should see this in the console:



Again, the correct request is sent, but the /login endpoint doesn't yet exist in our API, so we need to generate that token on the backend. Let's create another November action, this time called "login":

The logic for our login action on the back-end will be very simple as well:

  1. Check if there's a user in our database that matches the sent username

  2. If it exists, use bcrypt to compare that user's password with the sent password

  3. If it matches, generate a JWT token with the user's id and send it as a response

First, we'll install a little library for generating those JWT tokens we mentioned (Make sure you're in the November folder this time):

Now let's code that login action:

app/actions/login.js

And that's it! Back to the Chirper app and you should now be able to log in!


Success! We're automatically redirected to the Home page!


Using our authenticated user on other pages

Even though this all looks good, there's still a big problem on the rest of the pages: they're not actually using our token! The home page still uses a static id of 11 to fetch the user we want to display in the profile glimpse for example.


What we want is to fetch the user based on our newly generated token, not from a static number. In order to achieve this, we need to create an authorizer, which will attach the token that November has generated for us to every request we make through Ember Data, and then create a little service that will fetch the user's info (this will be done every time we start the app).


Similar to authenticators, authorizers are stored in an app/authorizers-folder. Since this is an authorizer for our OAUTH2 authentication, we simply name the file oauth2.js. Again, Simple Auth already has a little library that we can use for this type of authentication, so we can simply import that:

app/authorizers/oauth2.js

Then we edit the adapter-file we created earlier to make sure that the OAUTH2-token is included in all Ember Data requests:

app/application/adapter.js

Next, we're going to create our own service (we'll call it session-account) that fetches the logged in user:

app/session-account/service.js

This will send a request to localhost:9000/users?me=true, and the idea is that our November backend should return the model of the logged in user, using the token which is appended to every request we make.


In order to retrieve the token on the backend, we're going to use a tiny package called express-bearer-token in our November app:

Then we just add this to the rest of our Node.js middleware. By doing this, we'll be able to easily access our token with req.token:

app/middleware/index.js (at the bottom)

Now that we have the token in our req-object, we also need to decode it in order to see what userId it contains! For this, we'll make our own custom middleware by creating a new file in /app/middleware called user-from-token.js:

app/middleware/user-from-token.js

We'll add this to the rest of our middleware, right after the express-bearer-token. Your final app/middleware/index.js-file should look something like this:

app/middleware/index.js

Finally, we need to edit our SQL query slightly so that i can handle the URL /users?me=true :

app/controllers/user/list.js

Alright, the backend is ready! The users?me=true request should now return the logged-in user if a valid token is provided.


In our Ember app, we want to make sure that this request is triggered as soon as the app boots, so that we know as early as possible if the user is logged in or not. It would therefore make sense to call it in our application route:

app/application/route.js

If you refresh the page now, you should see that the request is indeed fired!



We also want this request to fire the first time we log in from the index-page, so let's also inject it to our index controller:

app/index/controller.js

We're almost there! Now we just need to edit some files on the frontend in order to show the session user we just loaded instead of the "statically-decided" user.


In order to be able to access sessionAccount.currentUser in our templates, we need to inject the session-account service in the corresponding controllers.

app/application/controller.js
app/application/template.hbs


If you see your user's username in the header after refreshing, then that means it worked!


We also need to replace model.user with sessionAccount.currentUser in the profile glance (on the home page). While we're at it, let's also add a simple logout-action to our home controller:

app/home/controller.js
app/home/template.hbs
app/home/route.js

Phew! That was a lot of work, but we finally have a working authentication, and hopefully you've learnt something in the process! Don't worry if it was a lot to take in at once. Just re-read the code again carefully and make sure you understand the flow of the app and how the frontend and backend work together. Also, check out Simple Auth's official docs.

Protecting your routes

Another common thing to have when dealing with authentication is protected routes which redirect the user based on if he's logged in or not.


In our Chirper app, we just need two behaviours:

  1. If the user is logged out and visits /home, he should be redirected to the index page where he can log in.

  2. If the user is logged in and visits index, he should be redirected to the home page.

This is easily done thanks to Simple Auth's AuthenticatedRouteMixin and UnauthenticatedRouteMixin. We'll start by setting the home-route as an authenticated route:

app/home/route.js

Next, we set the index-route as an unauthenticated route:

app/index/route.js

Finally, we need to specify where these authenticated and unauthenticated routes should redirect, which we do in config/environment.js . In some apps, you might for example want the AuthenticatedRouteMixin to redirect to a dedicated login route. In our case, we just want to take the user to index, since that's where our login form is. And we want logged in users to be redirected to home:

config/environment.js

Voila! You'll now notice that, if you're logged in and visit the index route, you'll be redirected to home, and if you're logged out and visit the home route, you'll be redirected to index. Perfect!

Fixing the chirp posting

As we mentioned in the previous chapter, the chirps that we try to post in our modal are still not saved to the database!


If you check the November console, you'll get a hint about why it's not working.


This is because the user -object that we're sending to November isn't handled correctly, so it reads the user_id as NULL (which isn't allowed in the database). However, we also want to stop sending the user in the request object (otherwise someone could potentially post as any user!) and instead use the token for setting the user_id!


Let's first fix the Ember frontend by removing the reference to the user object...

app/components/compose-modal/component.js

And then we fix the November backend...

app/controllers/chirp/add.js


We fixed it!


To sum it up: we've now implemented a signup, a login, and a system for fetching the user for every server request we make (which is handy when posting for example). It might have seemed very lengthy, but it's actually not too bad an achievement for just one chapter's work!


In the next chapter, we're going to improve our existing profile page by making it possible to view multiple profiles (right now you can only see your own).

Comments

Daniel Jeffery

Just wondering how/where the req object is getting access to the models property... I can see how it is getting access to req.body.username etc as they are coming from the post request ... but models?

Tristan Edwards

If you want to dig into the November file system, you'll see that this is set in the file app/middleware/sequelize.js. It's very common to attach things like models and users to the req-object, in order to have access to them everywhere. :-)

Daniel Jeffery

Cheers - I have asked a lot of questions here, as have others, so just wanted to say that I (and I am sure others) really appreciate the assistance - it's very helpful.

Daniel Jeffery

By the way that's really cool - and super handy to know that! Good to be ware of these little things rather than to just take it for granted that it works!

Adam Marinelli

For Windows users, installing bcrypt failed at step node-gyp rebuild ...had to mess around with node/npm version, not sure exactly what solved it. Installed node-gyp globally with npm install -g node-gyp which may have helped.

profile/avatar/default
Tim

This package is depricated and now distributed as 'ember-simple-auth'. The instructions here did not work for me, running the latest version of ember (2.2.0).

Tristan Edwards

@timherpderp: Thanks! I've updated the chapter now!

Kirk Israel

Based on "Posting new chirps" lesson, I think this needs to be this.get('application').send('toggleComposeModal'); and also include an explicit <p class="p1">application: Ember.inject.controller(),</p>

Kirk Israel

app/controllers/user/list.js is best thought of as an "SQL query"?

Daniel Jeffery

Hey Kirk, that is correct as far as I understand it, though my understanding is not yet complete of how the various aspects of November work

Daniel Jeffery

Hey hey, what's the this.send('login'); part doing here? :P

Tristan Edwards

@danieljeffery: Once you've signed up, it's nice to get automatically logged in as well. :)

Daniel Jeffery

It's the service that matters - makes sense!

Daniel Jeffery

Just figured out why it's called "NovEMBER" - a bit slow on that one!

Tristan Edwards

@danieljeffery: Also, the "cold November" nicely complements Ember's hot fire. ;-)

Daniel Jeffery

It does, this one should be set to be a winner then!

Alid Lorenzo Castano

Hey, can you clarify why you use ember data here, to create a new chirp, and ajax (above) for the signup/login (instead of just using ember data to create new user)?

Tristan Edwards

@alidlorenzocastano: Hey! Yes, Ember's store is used for creating, updating and destroying models. For AJAX requests that aren't deeply tied to a model, you should use other methods (there's a great addon called Ember Ajax that simplifies this). I guess it could be argued that the signup-action creates a user, but in this case we wanted to escape Ember Data's conventions about what data to send to what endpoint. Hope that helps!

Manvendra Singh

@tristan, could you please tell me where did you find this magic string 'authenticator:oauth2'? I searched the library but couldn't find it.

Manvendra Singh

Oh! I didn't checkout the Github. My mistake. Thanks a lot @tristan.

Tristan Edwards

@manvendrasingh: Here's the documentation. :) It's also explained in their README (scroll down a bit)

profile/avatar/default
Nicholas

Can you add a comment showing which file to add this code to?

Tristan Edwards

@nicholas: Fixed. Thanks for the heads up! :)

profile/avatar/default
Tim

I had to go over this chapter 3+ times until I figured out I missed adding ".extend(DataAdapterMixin...", not sure if others had this problem, may want to add a comment