I would like to implement authentication with ember.js.
So, when the application start, before the Router handles the requested url, I want to check the user status. If the user isn't authenticated, I want to save the requested url and redirect to a specific url (/login).
I tried to implement this overloading Ember.Route but I don't think it's a good practice.
For example, if i do:
var AuthRoute = Ember.Route.extend({
redirect: function() {
var controller = App.userController;
if (!controller.get("userAuth")) {
controller.set("lastFilter", this.routeName);
this.transitionTo("index");
}
}
}
});
If, the url is '/admin/foobar', the admin route will redirect instead of foobar.
Can I process redirection before the Router to start?
I use something like this
Ember.SecureRoute = Ember.Route.extend({
role: null,
redirect: function (model) {
if (!this.controllerFor('login').get('authenticated')) {
this._routeToLogin();
}
var role = this.get('role');
if (!Ember.isEmpty(role) && !this.controllerFor('login').hasRole(role)) {
this._routeToLogin();
}
},
_routeToLogin: function () {
var infos = this.get('router.router.currentHandlerInfos');
this.router.router.didTransition(infos);
var routeName = !this.router.router.hasRoute(this.routeName) ? this.routeName + '.index' : this.routeName;
var params = infos.filter(function (item, index, enumerable) { return item.context !== undefined; }).map(function (item) { return item.context; })
var url = Ember.Router.prototype.generate.apply(this.router, params.insertAt(0, routeName))
this.router.location.setURL(url);
this.transitionTo("login");
}
});
in your loginController you can then use the browser history to go back to your original route
APP.LoginController = Ember.Controller.extend({
//other stuff
authenticate: function (username, password) {
//do the authentication
history.go(-1);
}
});
The way that I do it is I pass down the authenticated user with my data. and I have an initConfiguration function inside of my main App
so inside of index file (in this case I am showing you jade) I have this:
// initialize ember settings
script(type='text/javascript')
App.initConfiguration('!{currentUser}')
and inside of my App file I have (coffeescript here)
window.App = Ember.Application.create
currentUser: null
initConfiguration: (currentUser) ->
if currentUser? and currentUser.length > 0
#set 'currentUser', currentUser
If you are using ember-data, then you have to change the application file to
window.App = Ember.Application.create
currentUser: null
tempCurrentUser: null
initConfiguration: (currentUser) ->
##
# set the tempCurrentUser to the currentUser passed in, this is then
# set in the ApplicationRoute to the App.currentUser
# and destroyed (this is necessary as the ember store is not initialzed yet
# and just setting it here will result in an error)
if currentUser? and currentUser.length > 0
#set 'tempCurrentUser', currentUser
and then inside your application route do the following
App.ApplicationRoute = Ember.Route.extend
setupController: (controller) ->
if App.tempCurrentUser?
App.setCurrentUser(App.tempCurrentUser)
Ember has a fantastic guide on preventing and retrying authentication: http://emberjs.com/guides/routing/preventing-and-retrying-transitions/
A simple way to do transitions based on whether or not a user is logged in:
App.ApplicationRoute = Ember.Route.extend({
willTransition: function () {
var session = this.controllerFor('session');
if (!session.get('authed')) {
this.transitionTo('login');
}
}
});
The example above assumes you have some kind of session controller or object managing the active sessions. This works because the ApplicationRoute is the very first route that is hit whenever you enter your application (from any URL).
Related
I am using ember-simple-auth (and ember-simple-auth-token, but I think this is not relevant). Whenever a logged-in user reloads the page, the session is properly re-created. I have one problem, though: when the user enters the credentials, I am running this code:
// controllers/login.js
import Ember from 'ember';
import helpers from '../lib/helpers';
function refreshActiveUser(store, session) {
store.find('user', 'me').then(function(user) {
Ember.set(session, 'activeUser', user);
});
}
export default Ember.Controller.extend({
session: Ember.inject.service('session'),
actions: {
authenticate: function() {
var identification = this.get('model.identification'),
password = this.get('model.password'),
authenticator = 'authenticator:token',
store = this.get('store'),
session = this.get('session');
session.authenticate(authenticator, {identification, password}).then(function() {
refreshActiveUser(store, session);
});
}
},
});
But I do not know how to trigger my refreshActiveUser on application reload. How can I listen to a "session initialized" event?
If I understand your issue correctly then you could make an observer for session.isAuthenticated
loggedIn: function() {
if(!this.get('session.isAuthenticated'))
return;
var store = this.get('store');
var profileLoaded = get_personal_settings(store);
var self = this;
profileLoaded.then(function(profile) {
// TODO: only used for user, save user, not the whole profile
self.set('session', profile);
});
}.observes('session.isAuthenticated').on('init'),
I have a situation where we used to have a login page but now we use a separate oAuth page. I'm trying to clean out a bunch of code, but I need to worry about people who have bookmarked the login route. Ideally, I'd like to be able to do something like this:
Router.map(function () {
this.route('login', {redirectTo: 'index'});
})
and then be able to get rid of the logic in my loginRoute:
var LoginRoute = Ember.Route.extend({
beforeModel: function (transition) {
var result = this._super(transition);
if (transition.isActive === false) {
// Route canceled for auth
return result;
} else {
return this.transitionTo('index');
}
}
});
Is that possible or do I absolutely have to keep my login route?
You can use ember-redirect addon which lets you do what you want:
let Router = Ember.Router.extend({
location: config.locationType,
redirects: {
login: 'index'
}
});
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.
I'm trying to load the current user into the data store but am having some difficulty. The server uses PassportJS and visiting /api/users/me returns a JSON object similar to this:
{"user":{"_id":"53a4d9c4b18d19631d766980","email":"ashtonwar#gmail.com",
"last_name":"War","first_name":"Ashton","location":"Reading, England",
"birthday":"12/24/1993","gender":"male","fb_id":"615454635195582","__v":0}}
My store is just defined by App.store = DS.Store.create();
The controller to retrieve the current user is:
App.UsersCurrentController = Ember.ObjectController.extend({
content: null,
retrieveCurrentUser: function() {
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
App.store.createRecord('user', data.user);
var currentUser = App.store.find(data.user._id);
controller.set('content', currentUser);
});
}.call()
});
It is called by my application controller:
App.ApplicationController = Ember.Controller.extend({
needs: "UsersCurrent",
user: Ember.computed.alias("controllers.UsersCurrent")
});
I suspect the line App.store.createRecord('user', data.user); is causing issues but I don't have any idea how to fix it.
The console logs TypeError: this.container is undefined while the Ember debugger shows every promise is fulfilled and the users.current controller has no content. Thankyou for any help you can provide.
Are you defining the store on the App namespace, because Ember Data doesn't do that by default. Either way, you're failing to define the type you want to find after you create the record.
var currentUser = controller.store.find('user', data.user._id);
createRecord returns the record, so there is no point in finding it afterward
var currentUser = controller.store.createRecord('user', data.user);
Also in your example, you are trying to call the function immediately on the type, and not on the instance. You should add that as a method to run on init.
App.UsersCurrentController = Ember.ObjectController.extend({
retrieveCurrentUser: function() {
console.log('hello')
var controller = this;
Ember.$.getJSON('api/users/me', function(data) {
var user = controller.store.createRecord('user', data.user);
controller.set('model', user);
});
}.on('init')
});
http://emberjs.jsbin.com/OxIDiVU/693/edit
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});
}