In any real-world Ember application, at some point, you'll need to deal with authentication. For example, users might need to send their credentials to identify themselves to the server, or authenticated users may need access to protected parts of the application.
An important aspect of authentication is protecting information based on the logged in user. This can be done by creating sessions with the use of tokens. In this recipe, we'll create a simple token-based authentication with an Express server. This will help us understand the basics. In the next section, we'll go over using OAuth2 with Ember Simple Auth.
$ ember g service session $ ember g adapter application $ ember g controller login $ ember g model student name:string age:number $ ember g route login $ ember g route students $ ember g template index $ ember g server index $ npm install body-parser –save-dev
This will generate all the scaffolding that we need for our application. The students
route will be protected. It will only populate information from the server if the user is authenticated.
The ember g server index
command will generate a node Express mock server for us. In the previous chapters, we used Ember CLI Mirage to do all our mock tests. The Express server generated by Ember is not as powerful. However, it will be easier to set up our fake server and authentication example. Be aware that when deploying an Ember application to production, the Ember server will not be included. It will, however, automatically start when running the ember serve
command.
// app/services/session.js import Ember from 'ember'; export default Ember.Service.extend({ token: null, authenticate(log, pass) { return Ember.$.ajax({ method: 'POST', url: '/token', data: { username: log, password: pass} }).then((info)=>{ this.set('token',info.access_token); }); } });
This service will be injected into our login controller. This will keep track of authenticated users and send Ajax requests to the server. The authenticate
method accepts the login name and password. These values are sent to the server using an HTTP POST
method to /token
. If the login information is correct, a token is returned and saved. If not, an error will be returned. We'll deal with the error in the login controller later.
The token
property will be used to keep track whether the user is authenticated or not.
index.js
file. Add a route for token
:// server/index.js /*jshint node:true*/ const bodyParser = require('body-parser'); module.exports = function(app) { app.post('/token', function(req, res) { if (req.body.username === 'erik' && req.body.password === 'password') { res.send({ access_token: "secretcode" }); } else { res.status(400).send({ error: "invalid_grant" }); } }); };
This is our node Express server. It will run when we start the Ember server. When the HTTP POST
/token
request is sent to the server, it will check the body username
and password
. For this example, we'll just hardcode them as 'erik'
and 'secretcode'
. If these match, it returns access_token
. If not, it returns an invalid message.
The access token will be saved in the session service. We can use this token to authenticate future requests to the server.
application.js
file. Add a new authorization header:// app/adapters/application.js import DS from 'ember-data'; import Ember from 'ember'; export default DS.RESTAdapter.extend({ namespace: 'api', session: Ember.inject.service(), headers: Ember.computed('session.token',function(){ return { 'Authorization': `Bearer ${this.get('session.token')}` }; }) });
In our application, accessing the students route will trigger a request to the server. The server will respond to authenticated users only. The server expects an authorization bearer header with every Ember Data request. We can do this using the headers
computed
property and returning the Authorization: Bearer
header with the secret token from the service. Every request to the server, using Ember Data, will send this header.
// app/server/index.js … app.use(bodyParser.urlencoded({ extended: true })); app.get('/api/students', function (req, res) { if (req.headers.authorization !== "Bearer secretcode") { return res.status(401).send('Unauthorized'); } return res.status(200).send({ students: [ { id: 1, name: 'Erik', age: 24 }, { id: 2, name: 'Suze', age: 32 }, { id: 3, name: 'Jill', age: 18 } ] }); }); …
Above app.post
, you can add app.get
for the students
route. Ember will trigger this HTTP GET request whenever it enters the /students
route. The server will check whether the request header has the secret code. If it matches, it returns the proper JSON data for the students
route. If not, it returns a 401 error.
// app/models/student.js import DS from 'ember-data'; export default DS.Model.extend({ name: DS.attr('string'), age: DS.attr('number') });
name
and age
:// app/routes/students.js import Ember from 'ember'; export default Ember.Route.extend({ model() { return this.store.findAll('student'); } });
/students
to retrieve the students model. As we defined the application to use the REST adapter, Ember will expect the data in the REST format:// app/templates/students.hbs Secret Student Information {{#each model as |student|}} {{student.name}}<br> {{student.age}}<br> {{/each}}
The students template uses the each
helper to iterate through the model returned from the server. It displays each name
and age
.
// app/templates/login.hbs <h3>Login</h3> {{input value=loginName placeholder='Login'}}<br> {{input value=password placeholder='Password' type='password'}}<br> <button {{action 'authenticate'}}>Login</button>
This template uses the input
helper for the loginName
and password
properties. The button triggers the authenticate
action.
authenticate
action:// app/controllers/login.js import Ember from 'ember'; export default Ember.Controller.extend({ loginName: null, password: null, session: Ember.inject.service(), actions: { authenticate(){ this.get('session').authenticate(this.get('loginName'), this.get('password')).then( ()=>{ alert('Great you are logged in!'); this.transitionToRoute('students'); },(err)=>{ alert('Error! Problem with token! '+ err.responseText); }); } } });
The controller has several properties. It retrieves the session information service and uses the authenticate
method to send the login information to the server. The authenticate
method returns a promise. If it's successful, the application transitions to the students
route. If it's not successful, an error is displayed in an alert box. In this example, we are using the ES6 arrow function. The ()=>
arrow function is a little shorter than using a function expression and it also lexically binds this variable.
// app/templates/index.hbs Welcome to my app! Login {{#link-to 'login'}}here{{/link-to}}
This is just a simple link to the login route.
'erik'
and password 'password'
. After clicking Login
, an HTTP POST request will be sent to the server with the name and password information. The Express server that we had set up earlier will respond with the token, and the session service will save it. The application will then transition to the students route. The following screen will be displayed:/students
. The Express server will check to make sure that the authorization bearer header has the correct secret code. It then will respond with the JSON data that Ember will display.Token-based authentication requires the client to send over credentials to the server. If authorized, the server then sends back a token that the client saves and uses on subsequent requests to the server. If the token is not present, the server may not return data to the client.
This recipe is a simple example of using authentication. It lacks proper error handling and session persistence. Nevertheless, it gives you an idea of how authentication works.