Ember app with server-side Google OAuth 2 Passport authentication (Node) - ember.js

My Ember app needs to authenticate users with Google OAuth 2. I want to be able to store the authentication tokens in a database, so I decided to put the authentication process on the server side, using Passport for Node.
When the authentication is over on the server, how do you make your Ember app aware of the Passport "session"?

Once authenticated thanks to the passport process, the client, in all its communications with the server, sends the user session along with its requests. If you want your Handlebars template to condition on the presence of a user, my approach was to set up the following request handler on the server:
app.get("/user", function (req,res) {
if (req.isAuthenticated()) {
res.json({
authenticated: true,
user: req.user
})
} else {
res.json({
authenticated: false,
user: null
})
}
})
And in my Ember route I do the following request:
App.ApplicationRoute = Ember.Route.extend({
model: function () {
return $.get("/user").then(function (response) {
return {user: response.user};
})
}
});
So that in my Handlebars template I can do the following:
{{#if user}}
<p>Hello, {{user.name}}</p>
{{else}}
<p>You must authenticate</p>
{{/if}}

Related

Ember simple auth how to update when authenticate data is avaiable

Ember app is using adfs login.
when a successful login adfs will redirect to ember web app with a route and query parameters. those query parameters contain
access_token
refresh_token
user_id
scope
i need to update session of the ember auth with those credential data. how can i achieve this ?
You will need to authenticate a session with you params.
In order to being able to authenticate your session you will need to create an authenticator. Basically, this will provide you some method to being able to handle your session (invalidateSession, authenticate, restoreSession etc..).
For the authenticator creation check out http://ember-simple-auth.com/api/classes/BaseAuthenticator.html
It will look like something like so https://jsfiddle.net/e7nzoa6y/ but that's not exclusive you will have to custom it with you endpoint and stuff
Then once you have your authenticator, check out the doc at
http://ember-simple-auth.com/api/classes/BaseAuthenticator.html
In your controller, after injecting the session service,
you will be able to call the function authenticate with something looking like
this.session.authenticate(
'authenticator:YOUR_AUTHENTICATOR', queryParams
);
djamel your answer works for me and i have modified the code using your example as below
import Base from 'ember-simple-auth/authenticators/base';
import {
isEmpty
} from '#ember/utils';
export default Base.extend({
authenticate(data) {
return new Promise((resolve, reject) => {
if(data.access_token!=null){
resolve({
access_token: data.access_token,
id: data.id,
agreement:data.agreement
});
}else{
reject();
} })
},
restore(data) {
console.log(data)
return new Promise((resolve, reject) => {
if (!isEmpty(data.access_token)) {
resolve(data);
} else {
reject();
} });
}
});
other than that i had to add
ENV['ember-simple-auth'] = {
routeAfterAuthentication: 'authenticated.index'
},
in config environment as well.

Ember Simple Auth - authenticated route model hook called before authenticate promise resolves

So I have a simple ESA setup with a custom authenticator/authorizer wrapping Basic HTTP Auth. The login route uses UnauthenticatedRouteMixin and the login controller is basically just authenticating against the custom authenticator, and afterwards fetches the user object from a REST service:
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
login() {
var self = this;
let { userName, password } = this.getProperties('userName', 'password');
this.get('session').authenticate('authenticator:basicauth', userName, password).then(function ()
{
self.store.queryRecord('user', { loginname : userName }).then( function (user) {
console.log(user.get('id'));
self.get('session').set('data.filterUserId', user.get('id'));
});
}).catch((reason) => {
this.set('errorMessage', reason.error || reason);
});
}
}
});
Now, in the first authenticated route (via AuthenticatedRouteMixin), for testing I also simply console.log the stored session property:
import Ember from 'ember';
import AuthenticatedRouteMixin from 'ember-simple-auth/mixins/authenticated-route-mixin';
export default Ember.Route.extend(AuthenticatedRouteMixin, {
session: Ember.inject.service('session'),
model: function () {
console.log(this.get('session.data.filterUserId'));
},
});
In the console output, I observe that the model hook seems to be executed before the authenticator's promise is resolved, i.e. first I get "null", then the ID.
How can I make the AuthenticatedRoute wait for the actual login authentication to resolve?
Ember Simple Auth will transition to the routeAfterAuthentication as soon as authentication succeeds which is right when the promise returned by the session service's authenticate method resolves. You're only loading the user asynchronously after that though so that it is only loaded after the transition as been done already.
For an example of how to load data when the session becomes authenticated see the application route in the dummy app: https://github.com/simplabs/ember-simple-auth/blob/master/tests/dummy/app/routes/application.js
You must be aware that the session can become authenticated when the user logs in in the current tab/window but also when the user logs in in another tab or window (where the sessionAuthenticated event could basically be triggered at any time then) - that's why you need to load all data you need when the session is authenticated whenever the sessionAuthenticated method is called on the application route (of course you can handle the session service's authenticated event yourself instead of using the ApplicationRouteMixin) and not when the promise returned by the session service's authenticate method resolves.

Ember Auth Custom Authenticator

When writing a custom authenticator using Ember.SimpleAuth with Ember-CLI, what exactly does the authenticate method of the custom authenticator need to return in order to establish a user as logged in? Below is the authenticator as it currently exists. We are using a phalcon rest api for the back end, so ultimately it seems that this method will need to hit that URL and authenticate the user on the server side, but what should the server return in order for ember.simpleauth to do what it needs to do?
import Ember from "ember";
import App from '../app';
import Base from "simple-auth/authenticators/base";
export default Base.extend({
tokenEndpoint: 'login',
restore: function(data) {
console.log('ran resotre');
},
authenticate: function(credentials) {
alert(credentials.identification);
alert(credentials.password);
},
invalidate: function() {
console.log('ran invalidate');
}
});
I would read Ember Simple Auth - API
authenticate needs to return a promise. Within the method, that promise needs to be resolved or rejected. A resolved promise would signal a successful authentication, while a rejected promise a failure in authentication. Here is how I structured a quick authenticate function.
authenticate: function (credentials, options) {
return new Ember.RSVP.Promise(function (resolve, reject) {
var loginPromise = Ember.$.post(<token url goes here>, {
username: credentials.identification,
password: credentials.password
});
loginPromise.then(function (data) {
resolve({
token: data.token,
userData: data.user
});
}, function (error) {
reject(error);
});
});
}

Synchronously inject current user after login

In my EmberJS application, I have a current user initializer, which injects the user into all controllers, routes, and views. It works great when logged in. I need the synchronous loading of the current user object so I can check some user permissions right off the bat.
Here's my initializer:
App.CurrentUserInitializer - Ember.Initializer.extent({
name: 'current-user',
after: 'authentication',
initialize: function(container, app) {
var store = container.lookup('store:main');
app.deferReadiness();
store.find('user', 'me').then(function (user) {
app.register('session:user', user, {instantiate: false});
_.forEach(['route', 'controller', 'view'], function (place) {
app.inject(place, 'currentUser', 'session:user');
});
app.advanceReadiness();
}).catch(function () {
app.advanceReadiness();
});
}
});
Where this breaks down for me, is during login. When the app boots, the initializer is run, but the /users/me route returns a 401 error. If I don't catch the error and advanceReadiness, booting halts. By catching the error, the app boots, but the initializer isn't run again after login, so the current user isn't loaded.
What are my options here? I can't use #marcoow's recommended method of adding a computed property to the Session, as I need the user loaded at boot time.
I've tried forcing loading the user object on the IndexRoute as a hack, but that doesn't seem to work.
Any tips are appreciated.
I'd register a session:current object with user property being null. That would be injected into controllers and routes (not sure injecting inside views is a good idea).
So at boot time the user is unknown, but the user lookup is done before the router goes deeper than application route, the root:
In the beforeModel hook of the application route, you'll load that current user. Then:
either you got the user and you set it this.set('session.user', model)
or you'll go in the error hook of the application route, in which case you'd have to check why, and if 401 then you can redirect the user to the login route this.transitionTo('login')
Don't forget to set a flag on session if you got the 401 so that transitionTo will make our user lookup of beforeModel to not happen again until we reach the login route
The code to be used to load the session user and initialise it could be placed in that session:current object so that you could call it from the application route or the login controller.
This is for example my session initialiser (not exactly as I explained, but loading in the initialiser, so closer to what you hve). I used a session model so that I do /session/current and then got a user into it (or not) which has the correct id and not me which then would make the store to load the same user with another id, and so have twice the same user as 2 different records:
app/models/session.js:
import DS from 'ember-data';
import Ember from 'ember';
export default DS.Model.extend({
user: DS.belongsTo('user'),
isAuthenticated: Ember.computed.bool('user.isClaimed')
});
app/initializers/session.js:
import Ember from 'ember';
export default {
name: 'session',
after: 'store',
initialize: function (container, app) {
var store = container.lookup('store:main'),
sid = Ember.$.cookie('sid');
// used to register a session
var register = function (session) {
app.register('session:main', session, {instantiate: false});
app.inject('route', 'session', 'session:main');
app.inject('controller', 'session', 'session:main');
};
// used to create a new session and trigger the backend to get details about it
// useful if the server is able to give an existing session while the browser doesn't know about it
// with external providers for example
var newSession = function () {
var session = store.createRecord('session');
// be sure to wipe out any invalid session ID
Ember.$.removeCookie('sid');
register(session);
return session.save().then(function (model) {
// if we got a valid new session, save its ID
Ember.$.cookie('sid', model.get('id'));
}).catch(function () {
Ember.debug('error saving new session: ' + Array.prototype.join.call(arguments, ', '));
});
};
// overall logic ==================
app.deferReadiness();
if (sid) {
// try to load the existing session
store.find('session', sid).then(function (model) {
register(model);
app.advanceReadiness();
}).catch(function () {
// there was a cookie for the session but it might have expired or the server invalidated it
Ember.debug('error loading session: ' + Array.prototype.join.call(arguments, ', '));
newSession().finally(function () {
app.advanceReadiness();
});
});
}
else {
// we don't have any trace of a session, let's just create a new one
newSession().finally(function () {
app.advanceReadiness();
});
}
}
};
app/router.js:
import Ember from 'ember';
var Router = Ember.Router.extend();
Router.map(function () {
this.resource('session', {path: '/'}, function(){
this.route('login');
this.route('logout');
});
});
export default Router;
app/templates/application.hbs (as an example):
<h2 id='title'>Welcome to my app</h2>
{{#if session.isAuthenticated}}
<a {{action 'session.logout'}}>Logout</a>
{{else}}
{{#link-to 'session.login'}}Login{{/link-to}}
{{/if}}
{{outlet}}
Then once in login controller, when the user actually logs in, the server will return the session model with the user linked into it, so the Ember binding magic will just update the session object.

Protecting a route using Firebase Simple Login

I'm trying to implement the following sequence of events in an Ember app that uses Firebase Simple Login and ember-cli.
Check if the user is authenticated before allowing entry to any route. All routes need to be authenticated.
If the user is not authenticated, redirect to LoginRoute. If the user is authenticated, allow them to enter the route.
In order to accomplish step 1, I reopen Ember.Route in an initializer and implement a beforeModel hook.
`import LoginController from "tracking/controllers/login"`
AuthInitializer =
name: 'authInitializer'
initialize: (container, application) ->
# Register LoginController with all controllers/routes
application.register 'main:auth', LoginController
application.inject 'route', 'auth', 'main:auth'
application.inject 'controller', 'auth', 'main:auth'
application.inject 'main:auth', 'store', 'store:main'
# Ensure user is logged in before allowing entry
Ember.Route.reopen
beforeModel: (transition) ->
#transitionTo 'login' if !#get('auth.isAuthed')
`export default AuthInitializer`
The above code does indeed redirect to login if the user is not currently logged in.
LoginController simply instantiates a new FirebaseSimpleLogin object and registers the appropriate callback function.
LoginController = Ember.ObjectController.extend
# Some of the controller is omitted for brevity...
auth: null
isAuthed: false
init: ->
dbRef = new Firebase('https://dawnbreaker.firebaseio.com')
#set('auth', new FirebaseSimpleLogin(dbRef, #authCompleted.bind(#)))
#_super()
authCompleted: (error, user) ->
if error
# Handle invalid login attempt..
else if user
# Handle successful login..
unless #get('isAuthed')
#set('currentUserId', user.id)
#set('isAuthed', true)
#transitionToRoute('index')
else
# Handle logout..
#set('currentUserId', null)
#set('isAuthed', false)
`export default LoginController`
There is two problems with my implementation.
When LoginController first initializes, isAuthed is set to false. Therefore, when authCompleted finishes, the app will always redirect to index.
The beforeModel hook executes before the authCompleted callback finishes, causing the hook to redirect to login.
What results from a page refresh is
The login template flashes for a second.
The app transitions to index.
This causes every page refresh to lose its current location (redirecting to the index route).
The question is, how can I protect a route using Ember and Firebase Simple Login? Any help would be appreciated.
Totally my opinion, but I like making the auth part of the resource tree. It doesn't need to be part of the url, but can be. Using this way, it can still use a global controller (it will be hooked up based on what's returned from the call, or some other hookup if you fetch it in the login).
App.Router.map(function() {
this.resource('auth', {path:''}, function(){
this.resource('foo');
this.resource('bar', function(){
this.route('baz')
});
});
this.route('login');
});
App.AuthRoute = Em.Route.extend({
model: function(params, transition){
var self = this;
// you can skip calling back with a local property
return $.getJSON('/auth').then(function(result){
if(!result.good){
self.transitionTo('login');
}
return result;
});
}
});
http://emberjs.jsbin.com/OxIDiVU/668/edit
In the case of a non-promise callback, you can create your own promise, and resolve that when appropriate.
model: function(){
var defer = Ember.RSVP.defer(),
firebase = new Firebase('https://dawnbreaker.firebaseio.com'),
fbLogin = new FirebaseSimpleLogin(firebase, this.authCompleted.bind(this));
this.setProperties({
defer: defer,
firebase: firebase,
fbLogin: fbLogin
});
return defer.promise.then(function(result){
// maybe redirect if authed etc...
});
},
authCompleted: function(error, user){
var defer = this.get('defer');
//if authenticated
defer.resolve({auth:true});
//else authenticated
defer.resolve({auth:false});
}