OAuth2 specifies authorization flows for web applications. We can use it with Ember to secure our application and provide data to only those users that are authorized. In this recipe, we'll look at using OAuth2 with Ember Simple Auth (ESA), a robust add-on for Ember.
ESA will handle our client-side session and authentication and send the requests to the server. It's very customizable and extensible. Although it can be complicated, just like our last recipe, we'll create a protected students route that can be accessed by authorized users only.
For the purposes of this example, we'll need an OAuth2 server. Setting up an OAuth2 server is beyond the scope of this recipe. There are several OAuth2 libraries out there that you can use to set one up. I recommend the following:
$ ember g adapter application $ ember g component login-comp $ ember g controller login $ ember g controller students $ ember g model student name:string age:number $ ember g route students $ ember g route application $ ember g route login $ ember g template index $ ember install ember-simple-auth
This will generate the scaffolding that we need to begin our application. The last command installs the add-on for ESA.
app
folder called authenticators
and authorizers
.oauth2-custom.js
to the authenticators
directory and application.js
to the authorizers
folder. Add this code:// app/authenticators/oauth2-custom.js import Authenticator from 'ember-simple-auth/authenticators/oauth2-password-grant'; import Ember from 'ember'; export default Authenticator.extend({ makeRequest(url, data) { var client_id = '123'; var client_secret = 'secret'; data.grant_type = 'password'; return Ember.$.ajax({ url: this.serverTokenEndpoint, type: 'POST', data: data, dataType: 'json', contentType: 'application/x-www-form-urlencoded', crossDomain: true, headers: { Authorization: "Basic " + btoa(client_id + ":" + client_secret) } }); } });
The authenticators
file is used by ESA whenever a user logs in. We can overwrite anything in the authenticator if needed. The makeRequest
method is used to send messages to the server. By default, ESA will make an HTTP POST
request to /token
with the username and password in the form field.
Authorization
Basic
with a secret client ID and client secret when authenticating for the first time with a server. To fix this, we can extend the makeRequest
method with our own Ajax request. This will be used when we log in:// app/authorizers/application.js import OAuth2Bearer from 'ember-simple-auth/authorizers/oauth2-bearer'; export default OAuth2Bearer.extend();
The authorizers
file is used by ESA to tell which type of authentication we are using. In this example, we are using Oauth2 as defined by OAuth2Bearer.extend()
.
application.js
file in the adapters
folder:// app/adapters/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' });
The adapter tells Ember to make all requests to the /api
namespace. The ESA DataAdapterMixin
is used to define the authorizer that the application will use. In this case, all Ember Data requests will use the OAuth2 application authorizer that we defined earlier. In other words, any request sent to the server using Ember Data will include the session data token, if it exists.
// app/templates/components/login-comp.hbs <h2>Login page</h2> <form {{action 'authenticate' on='submit'}}> {{input value=login placeholder='Login'}}<br> {{input value=password placeholder='Password' type='password'}}<br> <button type="submit">Login</button> </form>
This will submit the login
and password
to the authenticate
action setup in our component.
authenticate
action:// app/components/login-comp.js import Ember from 'ember'; export default Ember.Component.extend({ auth: Ember.inject.service('session'), login: null, password: null, actions: { authenticate() { this.get('auth').authenticate('authenticator:oauth2-custom', this.get('login'),this.get('password')).then(() => { alert('Thanks for logging in!'); this.get('transition')(); }, () => { alert('Wrong user name or password!'); }); } } });
As we are using ESA, we have access to a session
service. This session
service has an authenticate
method that uses authenticator
that we created earlier. In the preceding code, we used the this.get()
method to get login
and password
from our template. We then called the authenticate
method on our service, passing in our authenticator
.
If the server returns a successful message, then we call transition
, a method that is passed to the component. If not, an alert box pops up telling the user that their login was not successful.
// app/templates/login.hbs {{login-comp transition=(action 'loggedIn')}}
This calls the login component and passes in the parent action, loggedIn
:
// app/controllers/login.js import Ember from 'ember'; export default Ember.Controller.extend({ actions: { loggedIn(){ this.transitionToRoute('students'); } } });
This action transitions the application to the students
route. It's triggered only with a successful login. It's also the name of the action passed in the login page component.
// app/templates/students.hbs <h2>Students</h2> {{#each model as |student|}} <h3>Student: {{student.name}} </h3> <h3>Age: {{student.age}} </h3> {{/each}} <button {{action 'logout'}}>Log Out</button>
The template displays the information from the server using the each
helper. A logout
button action will log the user out:
// app/controllers/students.js import Ember from 'ember'; export default Ember.Controller.extend({ auth: Ember.inject.service('session'), actions: { logout(){ this.get('auth').invalidate(); } } });
logout
action invalidates the session. Invalidating the session revokes the token so that it is no longer available:// app/routes/students.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('student'); } });
This route returns all the information for the student
model. You'll notice that AuthenticatedRouteMixin
is added. This tells Ember to make this route available only if it's authenticated by the server. If it's not, it will route back to the application.
// app/routes/application.js import Ember from 'ember'; import ApplicationRouteMixin from 'ember-simple-auth/mixins/application-route-mixin'; export default Ember.Route.extend(ApplicationRouteMixin);
ESA's ApplicationRouteMixin
will catch any errors and transition to the login route.
// app/templates/index.hbs Hello! Want to login? Click {{#link-to 'login'}}here!{{/link-to}}
The link-to
helper links to the login
route.
$ ember serve –-proxy http://localhost:3000
The --proxy
argument tells Ember to proxy all server requests to localhost
at port 3000
. We'll assume, in this example, that the OAuth2 server is running on port 3000
in your local box.
A successful login will look like this:
It will then redirect to the students route. This route will send a request to the server with an authorization bearer request with the correct token. It will receive the student data so that it can be displayed to the user:
Accessing this route without being logged in causes redirection to the login page.
The Ember Simple Auth add-on manages the session, authentication, authorization, persistence, and communication with a server. It has its own built-in session service that makes it easy to manage.
OAuth2 is a specification of a type of flow when doing authentication in web apps. As Ember is a single-page application, there is not much security on the application side. It must rely on a server to authenticate and manage tokens. ESA makes this possible by handling all the work needed to send and communicate with the server.