Hi. I’m Matthew.
@mixonic
httP://madhatted.com
matt.beale@madhatted.com
201 Created
We build õ-age apps with Ember.js. We take
teams from £ to • in no time flat.
http://bit.ly/ember-toronto-edge
authentication
The Goal
•Auth against multiple 3rd party services
•Don’t be reloading the page
•keep the complexity off the server
One page, no reload
Sign In
Auth at Windows Live
Signed In!
OAuth2
•access to resources via tokens
•Several token types
•a different flow for each type
+----------+
| Resource |
| Owner |
| |
+----------+
v
| Resource Owner
(A) Password Credentials
|
v
+---------+ +--------...
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-...
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-...
+----------+
| Resource |
| Owner |
| |
+----------+
^
|
(B)
+----|-----+ Client Identifier +---------------+
| -+----(A)-...
Implicit Grant supported by
•facebook (behind sdk)
•google
•soundcloud
•box.net
•windows live
!
•& more
Building it!
Abstractions
•session manager (controller)
•Oauth adapter
•live, FB specific adapters
•popup manager
6 <div {{bind-attr class=":sign-in-menu isOpen:open:closed"}}>
7 <a class="sign-in-button facebook" href="#" {{action "sel...
36 signIn: function(service){
37 var session = this.get('session'),
38 route = this;
39 session.open(service)
40 .then(fun...
61 Ember.Application.initializer({
62 name: 'authentication',
63 initialize: function(container, app){
. . .
65 import Ses...
9 open: function(credentials){
10 var session = this;
11 session.set('isAuthenticating', true);
12
13 return this.get('ada...
61 Ember.Application.initializer({
62 name: 'authentication',
63 initialize: function(container, app){
. . .
70 import Aut...
50 // returns a promise that resolves to a user
51 open: function(serviceName){
52 var auth = this;
53 var authService = t...
25 var LiveAuthService = Ember.Object.extend({
26 open: function(){
27 return this.signIn()
28 .then( this.normalizeServic...
38 App.initializer({
39 name: 'Register Services',
40 initialize: function(container, app) {
41 registerServices(container...
65 open: function(url, options) {
66 this.closeExistingWindow();
67 this.rejectExistingDeferred('canceled');
68 var deferr...
30 createBoundMessageHandler: function(){
31 this.boundMessageHandler = function(event){
32 var matches, message = event.o...
Upon load, look for tokens
61 Ember.Application.initializer({
62 name: 'authentication',
63 initialize: function(container...
Upon load, look for tokens
99 readAccessToken: function(){
100 var accessToken, match,
101 regex = /access_token=([^&]*)/,...
promises complete!
Other ideas
•localstorage for auth tokens
•how to recognize a cancelled sign in?
•we still check the token is valid
server...
Try it
HereHere.co
Thanks!
@mixonic
httP://madhatted.com
matt.beale@madhatted.com
Upcoming SlideShare
Loading in …5
×

Client-side Auth with Ember.js

13,483 views

Published on

There are several platforms you can authenticate users against without using a server, among them Facebook (who provides a JavaScript SDK) and Windows Live (who provides Oauth2 and bearer tokens). With these services, we can implement authentication flows nearly entirely in Ember. With the example of a real project (http://herehere.co), let’s see how to do this using dependency injection, dependency lookup, promises, and routing hooks.

Published in: Technology

Client-side Auth with Ember.js

  1. 1. Hi. I’m Matthew.
  2. 2. @mixonic httP://madhatted.com matt.beale@madhatted.com
  3. 3. 201 Created We build õ-age apps with Ember.js. We take teams from £ to • in no time flat.
  4. 4. http://bit.ly/ember-toronto-edge
  5. 5. authentication
  6. 6. The Goal •Auth against multiple 3rd party services •Don’t be reloading the page •keep the complexity off the server
  7. 7. One page, no reload Sign In Auth at Windows Live Signed In!
  8. 8. OAuth2 •access to resources via tokens •Several token types •a different flow for each type
  9. 9. +----------+ | Resource | | Owner | | | +----------+ v | Resource Owner (A) Password Credentials | v +---------+ +---------------+ | |>--(B)---- Resource Owner ------->| | | | Password Credentials | Authorization | | Client | | Server | | |<--(C)---- Access Token ---------<| | | | (w/ Optional Refresh Token) | | +---------+ +---------------+ resource owner password credentials grant Do not use this
  10. 10. +----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI ---->| | | User- | | Authorization | | Agent -+----(B)-- User authenticates --->| Server | | | | | | -+----(C)-- Authorization Code ---<| | +-|----|---+ +---------------+ | | ^ v (A) (C) | | | | | | ^ v | | +---------+ | | | |>---(D)-- Authorization Code ---------' | | Client | & Redirection URI | | | | | |<---(E)----- Access Token -------------------' +---------+ (w/ Optional Refresh Token) Authorization code grant
  11. 11. +----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI --->| | | User- | | Authorization | | Agent -|----(B)-- User authenticates -->| Server | | | | | | |<---(C)--- Redirection URI ----<| | | | with Access Token +---------------+ | | in Fragment | | +---------------+ | |----(D)--- Redirection URI ---->| Web-Hosted | | | without Fragment | Client | | | | Resource | | (F) |<---(E)------- Script ---------<| | | | +---------------+ +-|--------+ | | (A) (G) Access Token | | ^ v +---------+ | | | Client | | | +---------+ implicit grant
  12. 12. +----------+ | Resource | | Owner | | | +----------+ ^ | (B) +----|-----+ Client Identifier +---------------+ | -+----(A)-- & Redirection URI --->| | | User- | | Authorization | | Agent -|----(B)-- User authenticates -->| Server | | | | | | |<---(C)--- Redirection URI ----<| | | | with Access Token +---------------+ | | in Fragment | | +---------------+ | |----(D)--- Redirection URI ---->| Web-Hosted | | | without Fragment | Client | | | | Resource | | (F) |<---(E)------- Script ---------<| | | | +---------------+ +-|--------+ | | (A) (G) Access Token | | ^ v +---------+ | | | Client | | | +---------+ implicit grant browser Facebook Auth website server website User Facebook connect
  13. 13. Implicit Grant supported by •facebook (behind sdk) •google •soundcloud •box.net •windows live ! •& more
  14. 14. Building it!
  15. 15. Abstractions •session manager (controller) •Oauth adapter •live, FB specific adapters •popup manager
  16. 16. 6 <div {{bind-attr class=":sign-in-menu isOpen:open:closed"}}> 7 <a class="sign-in-button facebook" href="#" {{action "selectService" "facebook"}}> 8 Sign In with Facebook 9 </a> 10 <a class="sign-in-button windows" href="#" {{action "selectService" "live"}}> 11 Sign In with Windows Live 12 </a> 13 <div class="mobile-show menu-footer subheader"> 14 {{link-to 'Leaderboard' 'leaderboard'}} 15 {{link-to 'About' 'map.about'}} 16 </div> 17 </div>
  17. 17. 36 signIn: function(service){ 37 var session = this.get('session'), 38 route = this; 39 session.open(service) 40 .then(function(){ 41 if (route.router.isActive('map')) { 42 route.disconnectOutlet({ 43 outlet: 'prompt', 44 parentView: 'map/neighborhood' 45 }); 46 } 47 var lastTransition = session.get('afterRedirect'); 48 if (lastTransition) { 49 lastTransition.retry(); 50 } else if (route.router.isActive('sign_in')) { 51 route.transitionTo(''); 52 } 53 }).catch(Ember.Logger.error); 54 },
  18. 18. 61 Ember.Application.initializer({ 62 name: 'authentication', 63 initialize: function(container, app){ . . . 65 import Session from 'appkit/controllers/session'; 66 container.register('session:main', Session); 67 app.inject('controller', 'session', 'session:main'); 68 app.inject('route', 'session', 'session:main'); . . . 95 }); inject session
  19. 19. 9 open: function(credentials){ 10 var session = this; 11 session.set('isAuthenticating', true); 12 13 return this.get('adapter').open(credentials, this) 14 .then(function(user){ 15 session.setProperties({ 16 isAuthenticated: true, 17 currentUser: user 18 }); 19 return user; 20 }).catch(function(err){ 21 if (err === 'canceled') { 22 return; // no-op 23 } else { 24 return Ember.RSVP.reject(err); 25 } 26 }).finally(function(){ 27 session.set('isAuthenticating', false); 28 }); 29 }, opening a session
  20. 20. 61 Ember.Application.initializer({ 62 name: 'authentication', 63 initialize: function(container, app){ . . . 70 import Auth from 'appkit/models/auth'; 71 container.register('session:adapter', Auth); 72 app.inject('session:adapter', 'store', 'store:main'); 73 app.inject('session:main', 'adapter', 'session:adapter'); . . . 95 }); Inject Auth adapter
  21. 21. 50 // returns a promise that resolves to a user 51 open: function(serviceName){ 52 var auth = this; 53 var authService = this.container.lookup('auth:' + serviceName); 54 55 if (!authService) { 56 return Ember.RSVP.reject('unrecognized service auth:' + 57 serviceName); 58 } else { 59 return authService.open() 60 .then( function(serviceData) { 61 // create a session 62 var sessionData = { 63 authData: { 64 name: serviceData.name, 65 id: serviceData.id, 66 accessToken: serviceData.accessToken 67 } 68 }; 69 var session = auth.get('store').createRecord('session', 70 sessionData); 71 return session.save(); 72 }).then( function(session){ 73 auth.set('authToken', session.get('id')); 74 75 return session.get('user'); 76 }); 77 } 78 }, open an auth attempt
  22. 22. 25 var LiveAuthService = Ember.Object.extend({ 26 open: function(){ 27 return this.signIn() 28 .then( this.normalizeServiceData ); 29 }, 30 31 signIn: function(){ 32 var url = createAuthUrl(); 33 return this.get('popup'). 34 open(url, {width: 500, height: 510 }); 35 }, 36 37 normalizeServiceData: function(accessToken){ 38 return { 39 name: 'live', 40 accessToken: accessToken 41 }; 42 } 43 }); open windows live auth attempt
  23. 23. 38 App.initializer({ 39 name: 'Register Services', 40 initialize: function(container, app) { 41 registerServices(container); 42 43 // force creation of FacebookAuthService (to load the FB global) 44 container.lookup('auth:facebook'); 45 46 app.inject('auth', 'popup', 'popups:authenticate'); 47 } 48 }); Inject popup service
  24. 24. 65 open: function(url, options) { 66 this.closeExistingWindow(); 67 this.rejectExistingDeferred('canceled'); 68 var deferred = this.generateNewDeferred(); 69 var defaultedOptions = this.applyDefaultOptions(options); 70 this.popup = window.open( 71 url, 'authentication', parameterizeOptions(defaultedOptions) 72 ); 73 74 if (this.popup) { 75 this.popup.focus(); 76 $(window).on('message', this.boundMessageHandler); 77 } else { 78 this.rejectExistingDeferred('failed to open popup'); 79 } 80 81 return deferred.promise; 82 },
  25. 25. 30 createBoundMessageHandler: function(){ 31 this.boundMessageHandler = function(event){ 32 var matches, message = event.originalEvent.data; 33 if (!message || !(matches = message.match(/^setAccessToken:(.*)/))){ 34 return; 35 } 36 37 if (matches[1]){ 38 Ember.run(this, function(){ 39 this.closeExistingWindow(); 40 this.resolveExistingDeferred(matches[1]); 41 }); 42 } 43 }.bind(this); 44 }.on('init'), Listen for messages from the popup
  26. 26. Upon load, look for tokens 61 Ember.Application.initializer({ 62 name: 'authentication', 63 initialize: function(container, app){ . . . 75 // Kind of feels like the in-popup logic should be elsewhere 76 var auth = container.lookup('session:adapter'); 77 var token = auth.readAccessToken(); 78 if (token && window.opener) { 79 // Don't go forward, we are just a popup with an accessToken 80 app.deferReadiness(); 81 window.opener.postMessage( 82 'setAccessToken:'+token, 83 Overherd.settings.origin 84 ); 85 } . . . 94 } 95 });
  27. 27. Upon load, look for tokens 99 readAccessToken: function(){ 100 var accessToken, match, 101 regex = /access_token=([^&]*)/, 102 hash = window.location.hash; 103 104 if (window.location.hash){ 105 hash = window.location.hash; 106 if (match = hash.match(regex)) { 107 return match[1]; 108 } 109 } 110 }
  28. 28. promises complete!
  29. 29. Other ideas •localstorage for auth tokens •how to recognize a cancelled sign in? •we still check the token is valid server-side
  30. 30. Try it HereHere.co
  31. 31. Thanks! @mixonic httP://madhatted.com matt.beale@madhatted.com

×