Angular JS Authentication maintaince - javascript

I'm looking for the best architectural solution.
I have following html:
<body ng-controller="individualFootprintController as $ctrl">
<div ng-hide="$ctrl.authenticated">
<h1>Login</h1>
With Corporate Account: click here
</div>
And controller:
function individualFootprintController($http) {
var self = this;
$http.get("/is_auth").success(function () {
self.authenticated = true;
})
.error(function() {
self.authenticated = false;
}
);
}
Questions:
1) Is this controller appropriate place for having this logic?
2) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once

Presumably authentication with the backend requires an actual token of some kind. I.e. you don't just set a true/false flag somewhere and call it authentication, but to be able to communicate with the backend you need to include a username/password/cookie/token in the request or the request will be denied.
A controller is a bad place to store such a thing, since controllers aren't permanent. Or at least you shouldn't make them permanent to the extent possible. Also, storing the token in a controller doesn't allow anything else access to it.
Whether you are logged in or not should be based on whether you have a valid authentication token or not.
This token must be stored in a canonical place, best suited for that is a service.
Other services get the token from there and also decide whether the app is currently "logged in" based on whether a token is available.
A rough sketch of how that should be structured:
app.service('AuthService', function () {
this.token = null;
});
app.service('FooService', function (AuthService, $http) {
$http.get(..., AuthService.token, ...)
});
app.controller('LoginStatusController', function (AuthService) {
Object.defineProperty(this, 'isLoggedIn', {
get: function () { return AuthService.token != null; }
});
});
<div ng-controller="LoginStatusController as ctrl">
<div ng-hide="ctrl.isLoggedIn">
When you actually log in and obtain a token, you set AuthService.token and it will be available to all other services and controllers. If and when the token becomes invalid or unset, all services and controllers lose their authenticated status.

What I usually do is the following :
Using ui-router
Take advantage of the resolve hook (which resolves some args and inject them into controller), and define my routes as subroutes of a main one which checks auth on each route change
scripts/services/user.js
angular.module('yourmodule')
.service('userSrv', function ($http, $q) {
var srv = {};
srv.getAuthenticatedUser = function() {
return $http.get("/authenticated_user");
};
return srv;
});
scripts/routes.js
angular
.module('yourmodule')
.config(function ($stateProvider) {
$stateProvider
.state('authenticated', {
abstract: true,
template: '<ui-view />',
controller: 'AuthenticatedCtrl',
resolve: {
signedInUser: function(userSrv, $q) {
return userSrv.getAuthenticatedUser()
.then(function(null, function() {
//Catch any auth error, likely 403
//And transform it to null "signedInUser"
//This is the place to handle error (log, display flash)
return $q.resolve(null);
});
}
}
})
.state('authenticated.myspace', {
url: '/myspace',
templateUrl: 'views/myspace.html'
});
});
Take advantage of $scope inheritance inside your view
scripts/controllers/authenticated.js
angular.module('yourmodule')
.controller('AuthenticatedCtrl', function (signedInUser, $scope, $state) {
//Here you set current user to the scope
$scope.signedInUser= signedInUser;
$scope.logout = function() {
//this is where you would put your logout code.
//$state.go('login');
};
});
views/myspace.html
<!-- here you call parent state controller $scope property -->
<div ng-hide="signedInUser">
<h1>Login</h1>
With Corporate Account: click here
</div>
1) I want to have actual "is_authenticated" value. How can make this happen, if I want to fire request only once
My solution asks for authenticated user on each route change. This seems strange but this is actualy viable and fast. The query should not be > 30ms it's a very small SELECT under the hood. THOUGH, asking "am I authenticated" and "get the authenticated user" are pretty the same thing, except one return a boolean, and the other return the user. I suggest, like I just show you, to handle "am I authenticated" question by requesting the authenticated user, then booleaning it with "if(user)" (null value handling).
2) Is this controller appropriate place for having this logic?
Yes and no. As you can see, the controller is the very place to "set the user to the scope thing", but scope inheritance allow you to not repeat it for each route. Though, http api logic should be ported to a service, and routing event ("get the authenticated user for this page, please" is a routing event IMHO) should be set in a separate file.
NB: if you need complete route "protection" (like redirection on non-authenticated, ask another question and I'll be glad to answer it)

Related

Where to put event listening in Angular

In my current module/component-based architecture I'm intercepting all responseErrors from API calls, and if I come across 401 Unauthorized, I'm redirecting to the logout page.
My current code is:
angular
.module('core')
.factory('authHTTPInterceptor', authHTTPInterceptor)
.config(['$httpProvider', function($httpProvider) {
$httpProvider.interceptors.push('authHTTPInterceptor');
}]);
function authHTTPInterceptor($rootScope) {
return {
'responseError': function (response) {
console.log(response.status);
if (response.status == 401)
redirectToLogout();
return response;
}
};
function redirectToLogout() {
window.location.href = 'logout.html';
}
}
I want to move redirectToLogout logic to another entity, and emit unathorized event in order to keep logic separated and unit test this properly.
My question is: what angular entity should be the one to listen for such event. Controller in this module, or component put in the html code like <redirectListener></redirectListener>, or something else? Or should I just create redirector service and inject here? What's best practice for this?
This is what we follow.
Instead of redirecting url to logout.html using js, you can define one route for it.
.state('logout', {
url: '/logout',
controller: 'LogoutController',
templateUrl: "/logout.html",
access: 0
})
And in logoutController, you can add your code.
by using this way, from any module if somebody is unauthorized then it will redirect to particular url and in logout logic will be at one controller.

Asynchronous global variables in AngularJS

I'm using AngularJS and I need to have a global several global objects that can be accessed by any controller in the app.
For example one object that i need is a user object that has the users id and other properties. This user object comes from the database via ajax. So I need a way to set that user object, then initialize the controllers used on the page. Basically giving the app a page load for the fist load of the program.
Then if that object isn't set, I need to redirect.
Does anyone have an idea of how to do this cleanly? I've been trying to use broadcast but its truing into spaghetti code.
Currently I use ui-router, and have a hidden view with a controller GlobalsCtrl. GlobalsCtrl uses a service to get the objects and then $broadcasts them so controllers can initialize. But.... This broadcast only works on the initial site load. When changing $location.paths that event is not broadcast because the GlobalsCtrl variables are already set.
I could add some if statements but that seems messy to me.
Please help. Thanks!
Plunker - http://plnkr.co/edit/TIzdXOXPDV3d7pt5ah8i
var app = angular.module('editor.services', []);
app.factory('AnalysisDataService', ['$http', function($http) {
var self = this;
self.activeAnalysis = {};
self.openAnalysis = function(analysisId) {
return $http.get('api/v1/assignments/analysis/' + analysisId)
.success(function(data, status, headers, config) {
self.activeAnalysis = data;
return self.activeAnalysis;
}).error(function(data, status, headers, config) {
console.log("Error could not load analysis article.");
}).then(function(result) {
return self.activeAnalysis;
});
};
self.getAnalysis = function() {
return self.activeAnalysis;
};
self.navigateToStep = function(step) {
$location.path('/analysis/summary');
};
return {
open: self.openAnalysis,
get: self.getAnalysis,
navigate: self.navigateToStep,
}
}]);
The problem is I need the self.activeAnalysis variable to be set before a few other controllers load. Each page loads a different data-set based on the analysisId.
ui-router has a resolve method that you can use in your routes.
The resolve will tell the routing to wait and resolve something before it moves to the new route.
Here are some examples(Your problem is similar to authentication):
angular ui-router login authentication
https://gist.github.com/leon/6550951
One thing you can do is to use a resolve on the route with ui.router, ensuring that any promises are resolved before transitioning to the new state. Something like this:
.state('app.mymodule', {url: '/my-route/',
views:{
'someView': {
templateUrl: '/views/someView.html',
controller: 'someController'
}
},
resolve: {
someResolve: function (someService) {
return someService.getUserInfo();
}
}
})
Here, the $http call done in someService.getUserInfo() will be resolved before transitioning to that state. Inside that method, just set the data from the response in the service, and it will be available in the controller.

Dynamic directive templates using app level resolve AngularJS

Currently i am implementing a completely stateless token-based authentication with AngularJS and a REST API as backend service. This works pretty well, but about one state i am currently not aware of and wanted to ask you how to handle it.
My intend is only to save my authentication token in the local storage from the client. On initial loading from the application i want to retrieve all user information from the REST API (GET /me) and save it in the $scope from my AngularJS project.
After implementing it, i have several problems to be sure that the retrieved user information are completely resolved and could work with the informations (like the access rights from the user).
I just want you to show an example where i ran into a kind of "asynchronous problem".
angular.directive('myDirective', ['MyAsyncService', function(MyAsyncService) {
return {
restrict: 'E',
templateUrl: function(element, attributes) {
console.log(MyAsyncService.getData());
}
}
}]
At this example my asynchronous service haven't the information yet, when the directive is rendered. Also retrieve the async data in the resolve function from ngRoute/ui-router or in the run() method from AngularJS couldn't solve my problem.
So back to my question ... It is better to save some information from the user on client side to avoid some of this problems? From my point of view it would be better not to save any kind of user information (like access rights, username, id, email address, ...) due security purposes.
How you handle you the authentication and authorization in a token-based AngularJS application?
Thanks in advance, hopefully you can get me back on track.
You need to resolve() authentication - especially this type of authentication at the app level.
If you aren't using ui.router - take a look.
When we resolve() on a route what we are saying is - do not do anything else until this thing is finished doing what it's doing.
This also assumes you are familiar with promises.
Your resolve dependency will ultimately expect that this asynchronous request is returning a promise so that it may chain controller logic after the callback finishes execution.
For example:
$stateProvider
.state('myState', {
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.simple = auth;
}
});
But there's more. If you want the resolve to happen at the begining of application execution for any route - you also need to chain your state declarations:
$stateProvider
.state('app', {
abstract: true,
templateUrl: 'app/views/app.tpl.html',
resolve:{
auth: function($http){
// $http returns a promise for the url data
return $http({method: 'GET', url: '/myauth'});
}
},
controller: function($scope, auth){
$scope.resolvedauth = auth;
}
}
.state('app.home', {
url: '/home',
templateUrl: 'home/views/home.tpl.html',
controller: function($scope){
//Should be available.
console.log($scope.$parent.resolvedauth);
}
});
Now that your children inherit from the app level resolve, you could control the template by doing something like this:
.directive('myDirective', function() {
return {
templateUrl: function(elem, attr){
return 'myDirective.'+attr.auth+'.html';
}
};
});
And the declaration itself:
<my-directive auth="resolvedauth.status" />

Redirect on all routes to login if not authenticated

How can I redirect to the login page if someone tries to hit any other route when they are not authenticated? Is there a "best" way to do this in AngularJS?
Seems like a common problem but I can't seem to find a way to do this. Thank you in advance for your help.
The best way to do this is to set up a '$routeChangeStart' listener which checks an 'authProvider' service function to verify that there is a user logged in. In our 'app.js' or in a separate file:
angular.module('myApp')
.run(['$rootScope', '$location', 'authProvider', function ($rootScope, $location, authProvider) {
$rootScope.$on('$routeChangeStart', function (event) {
if (!authProvider.isLoggedIn()) {
console.log('DENY : Redirecting to Login');
event.preventDefault();
$location.path('/login');
}
else {
console.log('ALLOW');
}
});
}])
Then for our 'authProvider' service:
angular.module('myApp')
.factory('authProvider', function() {
var user;
return {
setUser : function(aUser){
user = aUser;
},
isLoggedIn : function(){
return(user)? user : false;
}
};
});
This solution was created from an answer here on stack overflow.
Thank you #MohammadAwwaad
I am doing this a different way now, with Node.js & Express & the passport module, but before that, when I was using PHP, I did it with several Angular modules. I had an outside module on my <html> tag with ng-app, then ng-controllers at certain tags, like <body> & certain <div>s. In one of these ng-controllers, I had an authentication function, then I had an ng-if for login, logout, etc. If the user is not logged in, I hid the current page and ng-included the appropriate page. Otherwise, I ng-included the current page.
That was probably not the best solution, but I didn't want to use third-party modules. If you get confused or have any questions (I know I was pretty confused) just ask.

AngularFire seems to be deleting my objects on subsequent controller loads

I'm using AngularFire in a multiplayer game and it sure looks like AngularFire is deleting my objects after the first load of a controller.
I'm using browserify to concatenate my JS files together so all my modules look like CommonJS modules.
My controllers are all loaded via ngViews and defined routes. I'm trying to keep knowledge of what user objects in Firebase look like confined to a user service; hence, all AngularFire invocations for the user object live in the service.
Here's my HomeController:
module.exports = function(ngModule) {
ngModule.controller('HomeController',
function($scope, user) {
if (!$scope.user) {
return;
}
user.getInvitations($scope);
user.getGames($scope);
});
};
I'm using AngularFire's auth from an auth service I defined. When the user object comes in, it is stored on the $rootScope. Here's a snippet of my auth service:
ngModule.run(function(angularFireAuth, warbase, $rootScope) {
// Here's where Firebase does all the work.
angularFireAuth.initialize(warbase, {
scope: $rootScope,
name: 'user',
path: '/login'
});
});
My user service uses a gotUser promise that is resolved when the user is present on the $rootScope. Here's a snippet of my user service:
$rootScope.$watch('user', function() {
if (!$rootScope.user) {
return;
}
userRef = warUsers.child(getPlayerId());
userInvitationsRef = userRef.child('invitations');
userGamesRef = userRef.child('games');
gotUser.resolve();
});
// ...
getInvitations: function(scope) {
scope.invitations = [];
gotUser.promise.then(function() {
angularFire(userInvitationsRef, scope, 'invitations');
});
},
getGames: function(scope) {
scope.games = [];
gotUser.promise.then(function() {
angularFire(userGamesRef, scope, 'games');
});
}
When my HomeController is loaded by a user with no games and no invitations everything works as expected. The user creates a game and it shows up on the home screen.
If I then reload the page the user's game objects (from path /users/:userId/games/) are cleared. There is no code to remove these values in my application at the moment.
I thought there might be a reference to AngularFire hanging around and syncing the blank value I set in the user service, so I removed the scope.games = []; line. If I don't set the initial value on the scope in my service I get this error in the console: Uncaught Error: Firebase.set failed: First argument contains undefined.
I'm guessing this has something to do with my unfamiliarity with controller lifecycles under ngView, my mis-use of services to DRY up my Firebase/AngularFire references, and general AngularJS newbishness all rolled into a galloping ball of fail, but I'd appreciate any pointers anyone can provide.

Categories

Resources