TL;DR What is a good pattern for requiring a user to login in order to view certain pages in a Durandal Single Page Application (SPA)?
I need a system w
Attempting to implement the "navigate back" method described in my question I have decided against using this method because of the limitations I have described above.
I have found in practice that a method which works much better is to pass the URL to the page which requires authentication as a query string to the login page, which can then use this to navigate forward to this URL once a user is authenticated.
Below is an outline of an implementation of this method which I have adopted. I am still keen to learn about any other login patterns that people have adopted for use in Durandal applications however.
In main.js (or anywhere before router.activate() is called) I still guard the route which requires authentication:
router.guardRoute = function (instance, instruction) {
if (user.isAuthenticated()) {
return true;
} else {
if (instance && typeof (instance.preventAnonymous) === "boolean") {
if (instance.preventAnonymous) {
return 'login/' + instruction.fragment;
}
}
return true;
}
};
In shell.js:
return {
router: router,
activate: function () {
router.map([
...
{ route: 'login', title: '', moduleId: 'viewmodels/login', nav: true },
{ route: 'login/:redirect', title: '', moduleId: 'viewmodels/login', nav: true },
...
]).buildNavigationModel();
return router.activate();
},
...
}
In viewmodels/login.js:
viewmodel = {
...,
canActivate: function() {
if (!user.isAuthenticated()) {
return true;
}
return false;
},
activate: function(redirect) {
viewmodel.redirect = redirect || "";
},
loginUser() {
...
if (user.isAuthenticated()) {
router.navigate(viewmodel.redirect);
}
...
},
...
}
One minor negative of this method is the prescense of the page fragment to redirect to upon a succesful login in the application URL query string. If you do not like the prescence of this page fragment in the URL query string, local storage could easily be used instead to store this value. In router.guardRoute one could simple replace the line
return 'login/' + instruction.fragment;
with
/* Check for local storage, cf. http://diveintohtml5.info/storage.html */
if ('localStorage' in window && window['localStorage'] !== null){
localStorage.setItem("redirect", instruction.fragment);
return 'login/';
} else {
return 'login/' + instruction.fragment;
}
and our activate method could look like:
activate: function(redirect) {
if ('localStorage' in window && window['localStorage'] !== null){
viewmodel.redirect = localStorage.getItem("redirect");
} else {
viewmodel.redirect = redirect || "";
}
},