Client-side Auth with Ember.js

31
Hi. I’m Matthew.

description

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.

Transcript of Client-side Auth with Ember.js

Page 1: Client-side Auth with Ember.js

Hi. I’m Matthew.

Page 3: Client-side Auth with Ember.js

201 Created

We build õ-age apps with Ember.js. We take teams from £ to • in no time flat.

Page 4: Client-side Auth with Ember.js

http://bit.ly/ember-toronto-edge

Page 5: Client-side Auth with Ember.js

authentication

Page 6: Client-side Auth with Ember.js

The Goal

• Auth against multiple 3rd party services • Don’t be reloading the page • keep the complexity off the server

Page 7: Client-side Auth with Ember.js

One page, no reload

Sign In

Auth at Windows Live

Signed In!

Page 8: Client-side Auth with Ember.js

OAuth2

• access to resources via tokens • Several token types • a different flow for each type

Page 9: Client-side Auth with Ember.js

+----------+ | 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

Page 10: Client-side Auth with Ember.js

+----------+ | 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

Page 11: Client-side Auth with Ember.js

+----------+ | 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

Page 12: Client-side Auth with Ember.js

+----------+ | 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

Page 13: Client-side Auth with Ember.js

Implicit Grant supported by

• facebook (behind sdk) • google • soundcloud • box.net • windows live !

• & more

Page 14: Client-side Auth with Ember.js

Building it!

Page 15: Client-side Auth with Ember.js

Abstractions

• session manager (controller) • Oauth adapter • live, FB specific adapters • popup manager

Page 16: Client-side Auth with Ember.js

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>

Page 17: Client-side Auth with Ember.js

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 },

Page 18: Client-side Auth with Ember.js

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

Page 19: Client-side Auth with Ember.js

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

Page 20: Client-side Auth with Ember.js

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

Page 21: Client-side Auth with Ember.js

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

Page 22: Client-side Auth with Ember.js

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

Page 23: Client-side Auth with Ember.js

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

Page 24: Client-side Auth with Ember.js

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 },

Page 25: Client-side Auth with Ember.js

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

Page 26: Client-side Auth with Ember.js

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 });

Page 27: Client-side Auth with Ember.js

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 }

Page 28: Client-side Auth with Ember.js

promises complete!

Page 29: Client-side Auth with Ember.js

Other ideas

• localstorage for auth tokens • how to recognize a cancelled sign in? • we still check the token is valid server-side

Page 30: Client-side Auth with Ember.js

Try it

HereHere.co

Page 31: Client-side Auth with Ember.js

Thanks!

@mixonic

httP://madhatted.com

[email protected]