Last reviewed in May 2016 with Ember 2.5 Update to Ember Octane in progress!
Getting authentication up and running in Ember can be overwhelming.
Now that we understand how Services and token-based authentication work, we are ready to get started with a simple –yet powerful– framework called Ember Simple Auth.
In this episode, we will enhance our existing “Secretcodez” application.
Good news: since we essentially created a dumbed-down version of Ember Simple Auth (using the same concepts), the upgrade will be very smooth.
Let's begin by installing the add-on:
$ ember install ember-simple-auth
That's it. We are ready to tackle the upgrade of our sample app!
If you haven't gone through part 1 but would still like to follow this guide, make sure you get the code:
Some files listed below will be modified substantially. Most of the time you can copy that code onto your own app, replacing previous content.
To recap: Secretcodez is a silly app that displays nuclear missile activation codes only to logged in users. It requests an OAuth2 token from a small embedded web server in order to show the codes.
The core concept in the authentication mechanism is the Ember Service. In Secretcodez we called it authManager
. Ember Simple Auth (ESA) has its own service, named session
. We are going drop ours and use ESA's – but we'll keep the authManager
variable name.
Bust the service!
$ rm app/services/auth-manager.js
Meanwhile, in the login page component…
// app/components/login-page.js
export default Ember.Component.extend({
authManager: Ember.inject.service('session'),
actions: {
authenticate() {
const { login, password } = this.getProperties('login', 'password');
this.get('authManager').authenticate('authenticator:oauth2', login, password).then(() => {
alert('Success! Click the top link!');
}, (err) => {
alert('Error obtaining token: ' + err.responseText);
});
}
}
});
(Note that we are now passing in an Authenticator
, authenticator:oauth2
.)
An Authenticator
is defined by ESA as:
The authenticator authenticates the session. The actual mechanism used to do this might e.g. be posting a set of credentials to a server and in exchange retrieving an access token, initiating authentication against an external provider like Facebook etc. and depends on the specific authenticator.
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)
So let's create our OAuth2 authenticator:
$ mkdir app/authenticators
$ touch app/authenticators/oauth2.js
With the following content:
// app/authenticators/oauth2.js
import OAuth2PasswordGrant from 'ember-simple-auth/authenticators/oauth2-password-grant';
export default OAuth2PasswordGrant.extend();
This strategy effectively replaces our Ember.$.ajax
call to fetch the token at /token
! All the heavy work is now done by Ember Simple Auth!
At the moment, if no token is available when the secret
route is accessed, a 401 Unauthorized
error will be thrown (you can probably notice it in your console). This will happen during the findAll
call to the backend.
By mixing in AuthenticatedRouteMixin
we get that check for free:
// app/routes/secret.js
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
model() {
return this.store.findAll('code');
}
});
The application route was used to catch those errors and transition to the login
route. With ESA, we simply mix in ApplicationRouteMixin
and it will be handled for us.
// app/routes/application.js
import Ember from 'ember';
import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin';
export default Ember.Route.extend(ApplicationRouteMixin);
Finally, the application adapter had the authManager
injected and sent an Authorization
header. Again, ESA takes care of this for us:
// app/adapter/application.js
import DS from 'ember-data';
import DataAdapterMixin from 'ember-simple-auth/mixins/data-adapter-mixin';
export default DS.RESTAdapter.extend(DataAdapterMixin, {
namespace: 'api',
authorizer: 'authorizer:application'
});
Which brings us to Authorizer
s. What are they?
Authorizers use the session data aqcuired by an authenticator when authenticating the session to construct authrorization data that can e.g. be injected into outgoing network requests etc. Depending on the authorization mechanism the authorizer implements, that authorization data might be an HTTP header, query string parameters, a cookie etc.
In order to replace our Authorization: Bearer "some token"
header, we will leverage ESA's OAuth2Bearer
authorizer. Let's create:
$ mkdir app/authorizers
$ touch app/authorizers/application.js
With…
// app/authorizers/application.js
import OAuth2Bearer from 'ember-simple-auth/authorizers/oauth2-bearer';
export default OAuth2Bearer.extend();
That's it for the upgrade!
If we restart ember server
and check it out… the application behaves exactly the same! (Except it's much more solid.) We are now delegating a great deal of complexity to Ember Simple Auth!
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)
Those of us familiar with Devise (the Rails authentication solution) will recall the current_user
variable available to Rails’ controllers.
We know a user has authenticated when the isAuthenticated
property becomes true
. The plan is to fetch the current user whenever that happens.
Let's whip up a custom session
service (as well as a User
model to represent our user):
$ ember g service session
$ ember g model user email:string
We will extend ESA's Session
service and add a (computed) property called currentUser
:
// app/services/session.js
import DS from 'ember-data';
import ESASession from "ember-simple-auth/services/session";
export default ESASession.extend({
store: Ember.inject.service(),
currentUser: Ember.computed('isAuthenticated', function() {
if (this.get('isAuthenticated')) {
const promise = this.get('store').queryRecord('user', {})
return DS.PromiseObject.create({ promise: promise })
}
})
});
Since querying the backend for a user involves a promise, we return a PromiseObject
that will update our template when the promise resolves.
Neat! But… hold your horses! It's not as if we had an API endpoint for the current logged in user.
Typically this response depends on a cookie and a DB lookup. But let's quickly create a dummy response, which for now will live at /api/users
.
// server/index.js
// ...
app.get('/api/users', function (req, res) {
return res.status(200).send({ user: { id: 1, email: 'vladimir@kremlin.ru' }});
});
// ...
It's high time to make use of the feature! We will prepend a snippet to the secret page:
{{! app/templates/components/secret-page.hbs }}
{{#if authManager.currentUser}}
Logged in as {{authManager.currentUser.email}}
{{/if}}
<h1>OMG DA CODEZ!!</h1>
<ul>
{{#each model as |code|}}
<li><strong>{{code.description}}</strong></li>
{{/each}}
</ul>
Naturally –you guessed it– we need to inject the service into our secret page component:
// app/components/secret-page.js
export default Ember.Component.extend({
authManager: Ember.inject.service('session')
});
Loading our app, this is what we see:
Great, it works!
We have covered a fair amount of ground regarding authentication. I hope this has been useful enough for you to apply on your projects!
Remember the source code is on Github:
master
has the app finished in part 1esa
has the app finished here!For further information and docs, do head over to the Ember Simple Auth website.
A future installment may bring authorization with external OAuth2 providers.
Did this guide help you? Were you able to run the sample app? Let me know in the comments!
Snacks is the best of Ember Octane in a highly digestible monthly newsletter. (No spam. EVER.)