Reputation: 829
Looking to build web app in Node.js with ability for user to log in (authentication), which has 3 non secure pages (/home, /contact, /about) and one secure page (/admin). As an aside, I've been referencing the scotch.io Mean Machine book.
The issue I'm having is that I've build everything out, and the login mechanism works in that when I log in, I get directed to /admin; however, when I go to /admin in the URL without logging in, I can still access the page. I.e. I'm not sure where to put the actual protection.
A bit below on how I've laid out my app. Hoping for as much a conceptual answer to suggest how I should be doing things, rather than necessarily only a code answer.
Services:
Router:
angular.module('routerRoutes', ['ngRoute'])
.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/home.html',
controller: 'homeController',
controllerAs: 'home'
})
.when('/about', {
templateUrl: 'views/about.html',
controller: 'aboutController',
controllerAs: 'about'
})
.when('/contact', {
templateUrl: 'views/contact.html',
controller: 'contactController',
controllerAs: 'contact'
})
.when('/login', {
templateUrl: 'views/login.html',
controller: 'adminController',
controllerAs: 'login'
})
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin'
});
$locationProvider.html5Mode(true);
});
Controllers:
homeController, aboutController, contactController are generally empty for now
adminController:
.controller('adminController', function($rootScope, $location, Auth) {
var vm = this;
vm.loggedIn = Auth.isLoggedIn();
$rootScope.$on('$routeChangeStart', function() {
vm.loggedIn = Auth.isLoggedIn();
window.alert(vm.loggedIn); // this gives correct answer and works
Auth.getUser()
.success(function(data) {
vm.user = data;
});
});
vm.doLogin = function() {
vm.error = '';
Auth.login(vm.loginData.username, vm.loginData.password)
.success(function(data) {
vm.user = data.username;
if (data.success)
$location.path('/admin');
else
vm.error = data.message;
});
};
vm.doLogout = function() {
Auth.logout();
vm.user = {};
$location.path('/login');
};
});
And finally, below is my index.html (just the body):
<body class="container" ng-app="meanApp" ng-controller="adminController as admin">
<a href="/"><i class="fa fa-home">Home </i></a>
<a href="/about"><i class="fa fa-shield">About </i></a>
<a href="/contact"><i class="fa fa-comment">Contact</i></a>
<a href="/admin"><i class="fa fa-comment">Admin</i></a>
<ul class="nav navbar-nav navbar-right">
<li ng-if="!admin.loggedIn"><a href="/login">Login</a></li>
<li ng-if="admin.loggedIn" class="navbar-text">Hello {{ admin.user.username }}</li>
<li ng-if="admin.loggedIn"><a href="#" ng-click="admin.doLogout()">Logout</a></li>
</ul>
<main>
<div ng-view>
</div>
</main>
</body>
I won't paste the other html pages that get injected into since there isn't anything on them yet (the login.html has just the two input fields and submit button).
So a couple of questions:
Another nit:
Upvotes: 0
Views: 1273
Reputation: 249
I took a custom property route to secure the routes in my application. Every state change taking place is listened for and inspected if it has this property. If it has this property set then it checks if user is logged in, if they are not, it routes them to the 'login' state.
I used UI-ROUTER in my current project where I have implemented this. I made a custom parameter called "data" that I used within the route.
Within a .config block to declare my opening routes:
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'login/login.html',
controller: 'LoginController',
controllerAs: 'vm'
})
.state('home', {
url: '',
templateUrl: 'layout/shell.html',
controller: 'ShellController',
controllerAs: 'vm',
data: {
requireLogin: true
}
})
Then I add this to a .run on the application where I'm looking for ui-router's $stateChangeStart event and looking at my custom property ('data') on the state declaration:
$rootScope.$on('$stateChangeStart', function (event, toState, toParams, fromState, fromParams) {
var requireLogin = toState.hasOwnProperty('data') && toState.data.requireLogin;
if (requireLogin && !authService.isLoggedIn()) {
event.preventDefault();
authService.setDestinationState(toState.name);
$state.go('login');
}
if (toState.name !== 'login') {
authService.setDestinationState(toState.name);
}
});
In case you're wondering what the authService.setDestinationState does... it preserves the URL that the user was attempting to visit... once they successfully login it forwards them to that state automagically (see below):
function login() {
authService.authLogin(vm.credentials)
.then(loginComplete)
.catch(loginFailed);
function loginComplete(data, status, headers, config) {
vm.user = data;
$rootScope.$broadcast('authorized');
$state.go(authService.getDestinationState());
}
function loginFailed(status) {
console.log('XHR Failed for login.');
vm.user = undefined;
vm.error = 'Error: Invalid user or password. ' + status.error;
toastr.error(vm.error, {closeButton: true} );
}
}
Upvotes: 1
Reputation: 401
When you define your Admin route, you can define a property called resolve. Each property within resolve is a function (it can be an injectable function). This function should return a promise, the promise's result can be injected into the controller.
For more information on resolve, look at http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx.
You can use resolve as follows to do an authentication check.
var authenticateRoute = ['$q', '$http' , function ($q, $http) {
var deferred = $q.defer();
$http.get("http://api.domain.com/whoami")
.then(function(response) {
if (response.data.userId) deferred.resolve(response.data.userId);
else window.location.href = "#/Login"
});
return deferred.promise();
}]
// ...
.when('/admin', {
templateUrl: 'views/admin/admin.html',
controller: 'adminController',
controllerAs: 'admin',
resolve: {
authenticatedAs: authenticateRoute
}
});
With this you could pass the authenticated User Id through - even if null - and let the controller deal with it, if for instance, you want a contextual message. Else, you could do as above and only do so if there is a user Id from the authentication request, otherwise redirect to your login route.
Hope this helps! /AJ
Upvotes: 0