I am new to backbone and I want to implement a very simple auth using backbone router.
I am actually using only the router from backbone in my app. When I start the app I render a login view and I also init the backbone router (Backbone.history.start();)
If login succeeded I call router.navigate('mainmenu', { trigger: true, replace: false }); to navigate to a new route where I render the main menu, but when I click on the browser's back button I navigate back to the login view.
Before navigating to the previous view (the login view) I want to ask the user if he wants to logout first, and if logout process goes well, then he is redirected to the login view.
How can I achieve that? I checked few other questions, but the answer is too complicated for my use case. I just want to prevent users to navigate back to specific views if they're logged in.
#Dethariel thanks for the answer. I successfully implemented some kind of session, using the built-in Backbone router. I started with their small example snippet from the Backbone.Router execute method backbone router execute snippet and did something similar to bellow:
var Router = Backbone.Router.extend({
// define routes and calkbacks
// ....
// define routes and calkbacks
execute: function(callback, args) {
// execute will be called before the callback for each specific route
// get the next route in here
var nextRoute = Backbone.history.fragment;
if(user.LoggedIn()){
// check if nextRoute is '#login*'. I could make other checks as well
if(nextRoute.indexOf('login')>-1)
prompt('Log out?');
// else continue routing
else if (callback) callback.apply(this, args);
}
else if (callback) callback.apply(this, args);
}
});
This is very minimal, and I don't think it's the best or secure way, but it's a good starting point for me.
You can add a backbone route which will handle the login page (if you haven't done that yet). Once this route is hit, you do (pseudo-code follows):
if (user.isLoggedIn()) {
if (showLogoutPrompt().decision === "logout") {
user.logout();
}
}
Hope this helps.
Related
Basically the objective is render the account page if user is logged in, otherwise redirect to a login page.
I have the following routes:
App.Router.map(function() {
this.resource('account', { path: '/'});
this.route('login', { path: '/login' });
})
My current code tries to access a loggedIn attribute in the account controller in the route:
App.AccountRoute = Ember.Route.extend({
renderTemplate: function(controller) {
var loggedIn = controller.get('loggedIn'); // ERROR: controller undefined
if (!loggedIn) {
this.transitionTo('login');
}
}
});
Should I implement this logic in the router? Why is the controller undefined in my route? Thanks!
Here are a couple ideas that might help you:
Your controller does not always exist. It is created by Ember when it needs it the first time. You can use the Chrome extension for Ember debugging to see which controllers are already created. In your case it should be available though since you are in the renderTemplate hook. In general, redirects should be done either in the beforeModel hook or the redirect hook:
redirect: function () {
if (!this.controller.get('loggedIn')) {
this.transitionTo('login');
}
}
Consider moving the authentication logic into an Ember service (example). A service in Ember is simply a class that extends Ember.Object. You will have the ability to inject that service into all your controllers and routes so it will be always available.
Even better: consider using the excellent ember-simple-auth that handles both authentication and authorization. It will create a session service available everywhere in your app, so you will be able to do things such as:
// Ensures the user is authenticated
if (!this.get('session.isAuthenticated')) {
this.transitionTo('login');
}
Or even better (since you don't want to copy paste that stuff everywhere):
// This route is now authenticated!
App.AccountRoute = Ember.Route.extend(AuthenticatedRouteMixin, {
...
}
And many other cool things!
Also, I see that you are not using Ember CLI yet. I'd recommend it once you feel more comfortable with Ember. Ember CLI is the future of Ember, it comes with a slightly different syntax but lot of great things.
Ok, maybe I am going about this the wrong way. I am new to meteor and am used to building my apps in express. I have a route to /myaccount. Before rendering any templates I am calling an asynchronous method that checks if the user is an admin. The method is on the server. I tried putting the admin check in onBeforeAction, but I got the same result. What happens is that the login template flickers quickly on the screen before the admin template is loaded. Anyone know how to fix this so the login screen does not flicker on screen before the admin template is loaded? Here's my route:
Router.route('/myaccount', function() {
var _this = this;
var user = Meteor.user();
if(user) {
Meteor.call('checkIfAdmin', user.username, function(err, isAdmin){
if(isAdmin) {
_this.render('admin');
} else{
_this.render('myaccount');
}
});
} else{
this.render('login');
}
});
What I find strange is that in the iron router docs it says this about onBeforeAction in regards to their example of checking if a user is an admin:
"If the user is not logged in, the route function will never get called"
But the flickering that happened when I used onBeforeAction lead me to believe that it was calling the route function.
Well I am not sure I have the solution but I have still some remarks.
First if you are running this code on the server, your meteor.call is effectively asynchronous. And I do not know what happens at the end of your function for the rendering template. Maybe it renders your precedent template, that is to say login ?
But if you want to avoid the flickering of a template the time iron-router is working, the best thing to do is, I think, add a loading template :
Router.configure({
loadingTemplate: 'loading'
});
<template name='loading'>
// some css to render a 'loading feature'
</template>
So when you want to go on admin you will have this loading screen instead of login.
For the verification, it is true that I would use a hook or a plugin with onBeforeAction to do such thing as verification, I think it is cleaner with iron-router than a method call.
I have a Rails 4 App that uses Turbolinks and Backbone.js.
Basically, my router looks like:
var BookRouter = Backbone.Router.extend({
routes: {
'': 'index'
},
index: function(){
var bookView = new BookView({
el: ".wrapper"
});
}
});
$(function() {
if (Backbone.History.started == false) {
var bookRouter = new BookRouter();
Backbone.history.start({ pushState: true, hashChange: false });
}
});
$(document).on('page:load', function (){
Backbone.history.stop();
Backbone.history.start({ pushState: true, hashChange: false });
var bookRouter = new BookRouter();
});
Here's the scenario:
A user lands on http://website.com/, which takes the user to the index route, which loads a template into the .wrapper, which is shown in my routing code above. This works fine.
A user clicks on one of my Rails generated pages (for example I have a http://website.com/books route which shows all the books that users have created.) This also works fine.
The user is still on the http://website/books route. If you click on my logo, it takes you to the index route (the one generated by the backbone code above). This works fine.
NOW THE PROBLEM: If you go back to Step 3, and instead of clicking the logo, decide to hit the back button, and go back from the books route to the index route, the contents that get put inside the $el .wrapper are repeated TWICE. So for example, in my template, I have the words "Please choose a book to continue". If you went to Step 1, you'd just see the words once. But now, you see n set of words each time you go to /books and then hit the back button n times.
The problem here is the hack used to make Backbone.js play together with Turbolinks. I guess the source of this hack is this blog post which recommends restarting the history whenever a link is handled by Turbolinks. However, stopping the history doesn't clear existing routes. So what you get after handling the page:load event is two router objects handling the same routes. And Backbone.History will trigger every callback for every matching route when you go back, even when they are registered for the same route - meaning that the index callback will run twice, hence the content duplication.
One way to deal with it would be simply removing the duplicate router:
$(document).on('page:load', function (){
Backbone.history.stop();
Backbone.history.start({ pushState: true, hashChange: false });
});
This is sufficient to trigger the router whenever Turbolinks is invoked. However, there seems to be a cleaner way to make Backbone.History trigger the router. From what I can tell from the source code, this should work correctly as well:
$(document).on('page:load', function (){
Backbone.history.checkUrl();
});
I've run into an annoying issue when loading data asynchronously in an ember route's model callback. The issue seems to be that if the model method of my route returns a promise which is rejected then the route will never attempt to re-evaluate that route model. It just automatically returns the same rejected promise the next time it tries to go to that route without even trying to re-fetch the data!
I understand from this answer that an ember route will only call it's model method when trying to convert the url into a model. I'm guessing that in the case of routes with dynamic segments it may be called if it has never encountered that particular dynamic segment before.
Here is what I've got in my router setup.
window.App = Ember.Application.create({
LOG_TRANSITIONS: true,
LOG_TRANSITIONS_INTERNAL: true
});
App.Router.map(function() {
this.route('login');
this.resource('users', { path: '/users' }, function() {
this.resource('user', { path: '/:user_id' });
this.route('create', { path: '/create' });
});
});
And this is my route.
App.UserRoute = Ember.Route.extend({
model: function(params) {
// This returns a promise
return App.User.fetch(params.user_id);
}
});
I have some special handling for errors in my application route so that routes which fail due to authentication exceptions redirect the user to the login screen.
App.ApplicationRoute = Ember.Route.extend({
actions: {
sessionExpired: function() {
this.controllerFor('login').set("tokenExpired", true);
this.transitionTo('login');
},
error: function(err) {
if (err.type === "TokenException") {
this.send('sessionExpired');
}
}
}
});
The Problem
I navigate to the /users route
For some reason my token expires (inactivity, whatever...)
I navigate to the /users/1 route
The route's model method returns a promise which rejects and I am kicked out to the login screen
I log back in and try to navigate back to the /users/1 route
The route automatically just returns the same failed promise it did last time and I'm kicked out to the login screen. :(
I'm thinking that what I want is some way to clear all the evaluated route models after a user logs in. If this was a multi-user system and one user logs out and another user logs in on the same computer without refreshing the page then that new user shouldn't have routes automatically resolved from the previous user's session.
This seems to me like it would be a common problem yet I can't find any sort of app-wide invalidate cache method. How should I solve this?
I'm not sure where ember data stands on the cache clearing feature, but here is one way to do it
clearCache: function (type) {
var map = App.store.typeMapFor(type);
map.idToCid = {};
map.clientIds = [];
map.recordArrays = [];
map.findAllCache = null;
}
And here is an example as to how the ember firebase library handles a fail find using cache clearing.
delete store.typeMapFor(store.modelFor('user')).idToRecord[username];
Full example here:
https://github.com/firebase/emberFire/blob/master/examples/blog/js/app.js
For anyone else who finds this - I never found a way to reset the ember application and cause it to forget all resolved routes. I did find a few other work-arounds.
In the end, I opted to just window.reload() any time that a user logged out of the system or had their authentication token expire.
Authenticated URLs
Another reasonable approach would be to put a random unique id in the hash state. Essentially just do this.
Instead of a route like:
#/contacts/1
prefix every authenticated route with some kind of unique id
#/PyUE4E+JEdOaDAMF6CwzAQ/contacts/1
App.reset
I tried tried a number of things. One of the more promising things I tried was redirecting to the login screen and using the Application's reset method on my global App object. http://emberjs.com/api/classes/Ember.Application.html#method_reset
That didn't work though, it seems that even a reset Application remember's the models of any routes that it has resolved - weird.
If I navigate to a view by clicking on a link such as 127.0.0.1/#/project/1, the correct view gets displayed. However, if I call this url directly in the browser (or hit refresh), the view won't be displayed. What could be the reason for this behaviour?
The way I set up the Router is as follows:
var AppRouter = Backbone.Router.extend({
routes: { },
initialize:function () { }
});
var app = new AppRouter();
and then in every module (I'm using require.js), a route and handler will be added
app.route("project/:id", "showProject");
Could it be that the routes aren't registered yet and thus the callbacks won't be called?
Make sure that you are calling Backbone.history.start() after all of your routers are loaded/instantiated and routes defined: http://backbonejs.org/#History-start
Alternatively, you could stop the history with Backbone.history.stop(), and start it again. Then the added route(s) will be picked up.
BTW, you can test if the history is currently started with the boolean Backbone.History.started (note the capital 'H' is necessary).