The SANE stack is a set of tools that you can use to help create projects with Sails.js and Ember. An addon called sane-auth was created by the same authors. Its purpose is to give the user basic OAauth2-JWT authentication for the full stack.
Today we'll look at the sane-auth addon and create a simple login page and protected route. You can follow along with the code if you like, it's posted on Github.
Setup
As always we'll begin by installing the necessary tools to get started. If you already have sane-cli installed you might want to uninstall it. You'll need the latest beta release to get sane-auth working. To uninstall the sane-cli use npm uninstall sane-cli -g
First you should have node installed. If not check out my Ember CLI guide. After it's installed used npm to install Ember and Sails.
$ npm install ember-cli -g
$ npm install sails -g
After this is done we can install the beta version of sane-cli.
$ npm install -g sane-cli@beta
To create a new project we'll run these commands. We'll then install sane-auth.
$ sane new MyAuthProject
$ cd MyAuthProject
$ sane install sane-auth
$ sane up
Environment Problems
When I was trying to get my environment working I ran into some problems. At first I wasn't able to run sane up without errors, which is the command needed to start both the Ember and Sails servers. To fix this I uninstalled ember-cli, sails and sane-cli. Then I cleared the npm cache npm clear cache. Finally I installed ember-cli, sails and sane-cli again. This seemed to fix everything.
Keep in mind that the sane generator uses ember-cli version 0.27. I followed the steps in my Ember-cli guide to upgrade the project to the latest version of Ember. This isn't necessarily needed for this tutorial. It's a good idea though. The last step of upgrading the project is to run ember init. I had to run the sane install sane-auth again after the project was upgraded to get everything working.
After everything was done I ran sane up and it started the Ember and Sails servers without any errors. For now ignore the deprecation warnings. We'll deal with that in a future tutorial.
JSON Web Tokens
We'll assume if you got this far that you've run the sane up command without error. The sane-auth package uses JSON Web tokens. This is a type of token based authentication. After you've successfully logged in a token is provided to the client. This token is sent to the server on each request and is used to verify the identity of the client.
Sane-Auth Application
You probably noticed that after you run the sane install sane-auth command that it adds a sample authentication application for you. Let's take a look at what it adds on the Ember side.
├── adapters
│ └── application.js
├── app.js
├── components
│ └── login-panel.js
├── controllers
├── helpers
├── index.html
├── models
│ └── user.js
├── router.js
├── routes
│ ├── application.js
│ ├── register.js
│ └── user.js
├── serializers
│ └── application.js
├── styles
│ └── app.css
└── templates
├── application.hbs
├── components
│ └── login-panel.hbs
├── index.hbs
├── login.hbs
├── protected.hbs
├── register.hbs
└── user.hbs
From this file tree you can see it added several files.
Register
Let's take a look at a few of the files added to get an idea on what's going on.
// app/routes/register.js
import Ember from 'ember';
export default Ember.Route.extend({
model: function() {
return Ember.Object.create();
},
actions: {
createUser: function(model) {
var _this = this;
Ember.$.ajax({
url: '/api/v1/users',
type: 'POST',
data: JSON.stringify({
user: {
password: model.get('password'),
username: model.get('username')
}
}),
contentType: 'application/json'
}).then(function(/*response*/) {
_this.transitionTo('login');
}, function(xhr, status, error) {
_this.set('errorMessage', error);
});
}
}
});
This route has an action called createUser in it. It uses an ajax jQuery call to post the username and password to the sails server. It then transitions to the login page. This is important to understand, because this is how new users are added.
Below is the register template.
// app/templates/register.hbs
{{outlet}}
<h2>User Registration</h2>
<div>
<form {{action 'createUser' model on='submit' }}>
<div>
<label for="username">Username</label>
{{input id='username' placeholder='Enter your email' type='email' class='form-control' value=model.username}}
</div>
<div>
<label for="password">Password</label>
{{input id='password' placeholder='Enter password' class='form-control' type='password' value=model.password}}
</div>
<button type="submit">Register</button>
</form>
<div>
{{#if model.errorMessage}}
<strong>Registration failed:</strong>{{model.errorMessage}}
{{/if}}
</div>
</div>
The action 'createUser' is important. It's passing the model on submit. The model has username, and password in it, which is then passed to the Sails server.
Simple Auth
The application uses the simple-auth library to do it's authentication. It's being used in the application.js route file and the user.
// app/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin, {
});
// app/routes/user.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model: function () {
return this.store.find('user');
}
});
By using the simple-auth mixin we can ensure that data is only retrieved if the route is authenticated.
So how does the Ember client know if the user is authenticated or not? This is done when the user logs in or logs out of the application. To achieve this ember simple auth must be setup correctly in the environment.js file. Once again this is already setup for us with the sane-auth package. Here is what it looks like.
// environment.js
...
ENV['simple-auth'] = {
"authorizer": "simple-auth-authorizer:oauth2-bearer"
};
ENV['simple-auth-oauth2'] = {
"serverTokenEndpoint": "/api/v1/auths/login",
"serverTokenRevocationEndpoint": "/api/v1/auths/logout"
};
The Sails side has end points for login and logout. When the login route is entered on the server side sails sends back the JSON web token, if the user name and hashed password is located in the data store. Ember simple auth stores that web token and uses it whenever it needs to talk back to the server.
The application has a login component that handles the logging in.
{{yield}}
{{! login form; the fields must be named "identification" and "password"; the controller action is "authenticate" }}
<form {{action 'authenticate' on='submit'}}>
<div>
<label for="identification">Login</label>
{{input id='identification' placeholder='Enter Login' class='form-control' value=identification}}
</div>
<div>
<label for="password">Password</label>
{{input id='password' placeholder='Enter Password' class='form-control' type='password' value=password}}
</div>
<button type="submit">Login</button> or
{{#link-to 'register'}} Register{{/link-to}}
</form>
{{#if errorMessage}}
<div>
<strong>Login failed:</strong>{{errorMessage}}
</div>
{{/if}}
The action 'authenticate' is apart of the ember simple auth api. It authenticates the session with the configured authenticator. In this case oauth2-bearer. Here is how the component is setup in the application.
// app/components/login-panel.js
import Ember from 'ember';
import LoginControllerMixin from 'simple-auth/mixins/login-controller-mixin';
export default Ember.Component.extend(LoginControllerMixin, {
authenticator: 'simple-auth-authenticator:oauth2-password-grant'
});
The oauth2-password-grant is talked about more detail in this library page.
Sails Side
Let's take a quick peek on how sails does it's authentication.
One of the most important aspects of the server is to create the web tokens when a user logs in. Sane-auth uses the jsonwebtoken package to sign and verify the web tokens.
Here is what it looks like to sign.
// api/controllers/AuthController.js
...
function issueTokens(user, res) {
var expirationTimeInMinutes = sails.config.jwt.expiration_time_in_minutes;
var token = jwt.sign(user, sails.config.jwt.secret, {
expiresInMinutes: expirationTimeInMinutes
});
var refreshToken = jwt.sign(user, sails.config.jwt.refresh_secret, {
expiresInMinutes: expirationTimeInMinutes
});
res.send({
user: user[0],
access_token: token,
expires_in: expirationTimeInMinutes * 60, // because simple auth expects seconds
refresh_token: refreshToken
});
This method is called from the login route to sign and create a new token and send it back to the client. There is a lot more code on the server side, but for now we'll skip it and leave it for another tutorial. You can always check out the complete code on Github.
Creating a protected route
Let's create a protected route on the client.
$ ember g route protected
Then we'll update the route info.
// app/routes/protected.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin,{
});
To protect this route all we need to do is add the AuthenticatedRouteMixin. Now this route will only be available to logged in users.
Here is our template.
// app/templates/protected.hbs
{{outlet}}
This is a protected route and can only be accessed to logged in users!
{{#link-to 'index'}}Back{{/link-to}}
Then I added a link to the protected route in the index file.
{{outlet}}
<h2>Index</h2>
{{#if session.isAuthenticated}}
<p> Welcome, {{session.user.username}} {{session.username}}</p>
<a href="#" {{ action 'invalidateSession' }}>Logout</a>
{{#link-to 'protected'}}Protected Route{{/link-to}}
{{else}}
{{login-panel}}
{{/if}}
The index file will show the welcome message if the user is authenticated, else it will show the login panel. It will also have a link to the protected route.
If you try to access the protected route when your not logged in http://localhost:4200/protected then it will ignore the request until you login.
Keep in mind that since this is a client side application that someone could probably find a way to remove the security and expose the protected route. What they won't have access to is the server data that will be returned to the client inside the route. This data should be protected and only be sent back to the client if the web token is verified. This is a little beyond the scope of this tutorial. I will be writing about it the future. In the mean time you can check out Scotch.io. They created an excellent tutorial on node.js authentication and explained how to verify the web token using jwt.verify. You should be able to do the same in Sails.
Demo
I don't have this up live on a server anywhere so you'll have to take my word for it. Of course you can try for yourself by using the code on github. These gifs below show a basic idea on how it works.
Future
In the future I'd like to take a look at other authentication strategies with Ember. I really like to try Firebase and Auth0. Both look like fun tools to use.
Image Credit Easy-QA
Questions? Tweet me at @ErikCH