AngularJS - evaluate a cookie in routes & template - javascript

I'm trying to sort out a login system using cookies so that the user's login will persist after leaving the app. I am able to set the cookie correctly, but I am unclear as to how I can use the stored cookie to limit the users access to the login screen if they are already logged in.
I think the best way to do this would be within the routes. This is what my file currently looks like:
var routes = angular.module('we365', ['rcForm', 'ngCookie', 'ngCookies']);
routes.config(function ($routeProvider) {
$routeProvider
.when('/login', {
templateUrl: 'views/login.html',
controller: 'loginCtrl'
})
.when('/', {// get digest view
templateUrl: 'views/getDigest.html',
controller: 'GetDigestCtrl'
})
.when('/artifact/:artifact_id', {// single artifact view
templateUrl: 'views/artifact.html',
controller: 'artifactCtrl'
})
.otherwise({
redirectTo: '/'
});
});
Also, i'd like to hide the 'login' button from the parent view so that the user isn't able to click on it. This is what the view looks like now:
<div class="container">
<div class="page-header col col-lg-12">
<h1>Welcome!</h1>
Login
Load Digest Data
</div>
</div>

There are many ways, I have two that are my favorite:
1) Check on route change
angular.module('MyApp', [])
.run(function($rootScope, myLoginService) {
$rootScope.$on('$routeChangeStart', function () {
if (!myLoginService.isUserLoggedIn()) {
$location.path('/login');
}
});
You can replace the isUserLogged for a mapper service that receives where the user wants to go; if the user has the proper privileges (either stored within a cookie or local storage in the form of a token), then let the route succeed. Otherwise, show an error, or route him to wherever you want. In my case, the myLoginService checks for a localStorage.
2) Any data request to a server has a token included to headers; failed requests (401) are intercepted and stored, while the user is redirected
This one is more for CRUD apps and not necessarily for routing, but the concept is simple: a user A can perform N actions as long as he/she has the privileges to do so; if he tries to perform an action (or M actions) that he's not allowed, then the request is intercepted and queued in order to ask him to authenticate with a user that CAN do those actions
.factory('securityInterceptor', ['$injector', 'securityRetryQueue', function($injector, queue) {
return function(promise) {
// Intercept failed requests
return promise.then(null, function(originalResponse) {
if(originalResponse.status === 401) {
// The request bounced because it was not authorized - add a new request to the retry queue
promise = queue.pushRetryFn('unauthorized-server', function retryRequest() {
// We must use $injector to get the $http service to prevent circular dependency
return $injector.get('$http')(originalResponse.config);
});
}
return promise;
});
};
}]);
Again, this is for more "data like" requests and not necessarily for routing. This was stolen from the AngularJS sample app. You should check it for more examples.

Related

Angularjs browser back to previous website

I have an issue with the browser back button on our Angularjs application. Here's the basic setup. We multiple pages (really states...) that perform a validation of the token on the client computer. Here's an example of one such state:
theApp.controller('someController',
['$scope', '$http', '$state', '$stateParams', '$log', 'authenticationFactory','myFactory',
function ($scope, $http, $state, $stateParams, log, authenticationFactory, myFactory) {
log.debug('someController');
authenticationFactory.validateToken()
.then(success => {
return filesFactory.getFileContentDetails($stateParams.fileId);
})
.then(response => {
$scope.stuff = response;
})
.catch(error => {
$state.go('login', { error });
})
.finally(() => {
spinnerFactory.hide('loading');
});
}]);
Here is a snippet of the defined login state:
$stateProvider
.state('login', {
url: '/login',
templateUrl: 'pages/login/login.html',
controller: 'loginController',
params: {
error: null
}
}).state('someresource', {
url: '/someresource/:fileId',
templateUrl: 'pages/someresource/someresource.html',
controller: 'someController'
});
And, here is some of the login controller:
theApp.controller('loginController',
['$scope', '$state', '$log', 'authenticationFactory', 'statusPopupFactory',
function ($scope, $state, log, authenticationFactory, statusPopupFactory) {
log.debug('loginController');
if ($state.params.error) {
log.error($state.params.error.data);
statusPopupFactory.setStatus('danger', $state.params.error.data);
} else if (localStorage.token) {
authenticationFactory.validateToken()
.then(user => {
$state.go('home');
})
.catch(error => {
log.error(`Token validation error: ${error.data}`);
});
}
}]);
So as it is, everything behaves as it should... can login, states transition, etc. You can hit URL directly which would bring up the state controlled by someController, rather than going through the normal app menuing system (ie, I could just navigate to https://theApp/someresource/1, which is run by someController).
When this is done, and they do not have a token (or it's expired or whatever other reason...), they get bumped to the state login. At this point, if they hit the browser back, the browser is trying to redirect them to the previous state in which they came from, rather than the website they came in from. When it tries to go to the previous state, it merely attempts to do revalidation of token, that fails, and just bounces them back to login once again. So it's a cyclical loop.
The issue is that from the "login" page at this point, they can't actually navigate back to the previous website. It feels like they are "trapped" in the site, never to get out of the login page unless you simply manually change the URL or close tab, etc.
How can I go about solving this?
My initial thoughts were that when the login state loads, I could somehow look at all the browser history, and remove all previous states for the application, but preserve any states that are not from the same host url. That way from login, they could "browser back" just fine. However I've exhausted lots of searching in trying to figure this out and... thus here I am.

Angular/Express: Redirecting to the last page visited after logging in

A similar-ish question here (but not applicable).
In my SPA I'm using PassportJS to handle authentication. I have many routes which require the user to be logged in. I route like this:
// ... other routing stuff
when('/insights', {
templateUrl: 'partials/insights',
controller: 'insightsCtrl',
resolve: {
loggedin: checkLoggedin
}
})
var checkLoggedin = function($q, $timeout, $http, $location, $rootScope){
var deferred = $q.defer();
$http.get('/loggedin').success(function(user){
if (user !== '0') {
$rootScope.user = user;
deferred.resolve();
}
else {
deferred.reject();
$location.url('/login');
}
});
return deferred.promise;
};
My route to check if logged in:
app.get('/loggedin', function(req, res) {
res.send(req.isAuthenticated() ? req.user : '0');
});
If they're not logged in, they're redirected to /login. Once that is successfully done though, I'd like to go back to the original page they tried to request.
Is there a built-in way to store the route the user is trying to access so that I can redirect to it once they're logged in? I can do this with a cookie I suppose, but it feels a little clunky to do so...
There is no built in approach besides using the custom callback that PassportJS provides, but if you are writing this for a SPA, you could leverage $cacheFactory and create a service/factory that stores the last visit page of that user.
Limitations for $cacheFactory are the same as local storage which are that they are domain specific, so if you change domains, your local storage will not be the same cross-domain.
You can use a custom callback as explained in passportjs documentation.
With custom callback, you can send the current query string to your server, and get a redirect to same query string after successful login.
Another possibility is to use localStorage to save current state and take it after login.
You have many modules to use localStorage with angular like angular-local-storage.

Angular JS authorization - check user role before loading the page

I'm writing cms front-end in Angular JS and I don't know how to deal with authorize user to see particular content when user first load the app. I have working solution when user is already log in and just navigate from page to page. I do it this way:
angular.module("myApp")
.run ($rootScope, AUTH_EVENTS, AuthServ) ->
$rootScope.$on '$stateChangeStart', (event, next) ->
if next.data
authorizedRoles = next.data.authorizedRoles
unless AuthServ.isAuthorized(authorizedRoles)
event.preventDefault()
if AuthServ.isAuthenticated()
$rootScope.$broadcast(AUTH_EVENTS.notAuthorized)
else
$rootScope.$broadcast(AUTH_EVENTS.notAuthenticated)
When user change the route, the AuthServ.isAuthorized(authorizedRoles) is fired.
When user logs in I get token and user data from server. Token is stored in local storage and user data are stored in memory (in the scope of top level controller). In the user data I have an info about his role, so I can check if he's authorized to see particular content.
Now let's assume that user is logged in and he reload the page. I still have the token as this is in localStorage but I loose user data (so I don't know what is his role). I don't want to show any content to user before I get his data again from server. So my question is how to resolve this ? Where and when should I make the request to server for user data ?
I thought that solution could be to manually bootstrap the app. I tried to do something like this:
app = angular.module('myApp', [])
fetchData = ->
injector = angular.injector(['ng'])
$http = injector.get('$http')
API_URI = injector.get('API_URI')
$localStorage = injector.get('$localStorage')
$http.get("#{API_URI}/users/me?token=#{$localStorage.token}")
.success (data) ->
app.constant('USER_DATA', data)
bootstrapApplication = ->
angular.element(document).ready ->
angular.bootstrap(document, ['myApp'])
fetchData().then(bootstrapApplication)
The problem with this code is that I don't have access to $localStorage service and API_URI constant. I need them to get token and to dynamically change url (development, production).
So what is the best solution ? Maybe storing user role in local storage as well ?
Any help would appreciated, Thanks.
You can use Angular's resolve in your routes configuration. Here is an example where I am resolving 'user' for a particular route. It must resolve before angular will load this route.
app.config( [ '$routeProvider', '$locationProvider',
function( $routeProvider, $locationProvider ) {
$routeProvider
.when('/somePath', {
templateUrl: "someView.html",
controller: "someController",
resolve: {
user: function( authService ){
return authService.getUser();
}
}
}
);
}
]);
Here is the service(factory in this case) to go along with it. I am checking to see if auth.user exists first so there won't be a request on each route change:
app.factory('authService', [ '$http', '$q',
function( $http, $q ) {
var pub = {};
pub.user = null;
pub.getUser = function() {
var deferred = $q.defer();
if( pub.user ) {
deferred.resolve( pub.user );
}
else{
$http.get('/someAuthUrl').then(function( user ) {
pub.user = user;
deferred.resolve( user );
});
}
return deferred.promise;
};
return pub;
}
]);
And then finally the controller which will not load until the auth function has resolved with your needed user data. You can access the user object now through injection.
app.controller( 'someController', [ '$scope', 'user',
function( $scope, user ) {
//Controller runs here only when user is resolved.
// Anything resolved will be passed in as your last dependency.
$scope.user = user;
}
]);
In this case you could have injected the service into the controller instead and use authService.user as we know it's available.
app.controller( 'someController', [ '$scope', 'authService',
function( $scope, authService ) {
$scope.user = authService.user;
}
]);
This is because whenever you reload/navigate new instance of controller is created and your user data in controller is lost. Best way to do is to move the logic of authenticating and storing data to a Service and use that service in the controller, this way you won't lose the data on refresh/navigation

Protecting routes with authentication in an AngularJS app

Some of my AngularJS routes are to pages which require the user to be authenticated with my API. In those cases, I'd like the user to be redirected to the login page so they can authenticate. For example, if a guest accesses /account/settings, they should be redirected to the login form.
From brainstorming I came up with listening for the $locationChangeStart event and if it's a location which requires authentication then redirect the user to the login form. I can do that simple enough in my applications run() event:
.run(['$rootScope', function($rootScope) {
$rootScope.$on('$locationChangeStart', function(event) {
// Decide if this location required an authenticated user and redirect appropriately
});
}]);
The next step is keeping a list of all my applications routes that require authentication, so I tried adding them as parameters to my $routeProvider:
$routeProvider.when('/account/settings', {templateUrl: '/partials/account/settings.html', controller: 'AccountSettingCtrl', requiresAuthentication: true});
But I don't see any way to get the requiresAuthentication key from within the $locationChangeStart event.
Am I overthinking this? I tried to find a way for Angular to do this natively but couldn't find anything.
What I did is implement an angularjs interceptor which handles http request errors. Basically, what I do is when I get the 401 (unauthorized) from my backend, I save the current url and redirect the user to a login page. When the user does login successfully, I retrieve the saved url to set the path with $location.
app.config(function ($routeProvider, $locationProvider, $httpProvider) {
/* Global interceptor for 401 - not authorized */
var interceptor = ['$location', '$q', 'authorizationService', function ($location, $q, authorizationService) {
function success(response) {
return response;
}
function error(response) {
if (response.status === 401) {
authorizationService.saveUrl($location.path());
$location.path('/logon');
return $q.reject(response);
}
else {
return $q.reject(response);
}
}
return function (promise) {
return promise.then(success, error);
};
} ];
});
In the logon controller (on successful logon) I set the location like this:
$location.path(authorizationService.getUrl());
I use angular-http-auth in my project. If you have a backend is easier to delegate on it to handle the authentication, showing a login form automatically in each 401 response, than duplicate the authentication mapping both on client and server.

What is the best way to safely process a login using AngularJS

I'm new to Angular. I'm developing a simple login form whereby the username typed is compared to a username returned from a JSON query. If a match is found, the login is processed.
I feel the way I'm doing it isn't safe, am I right in thinking that the returned JSON string could be accessed through the browser's console?
I will be adding password check to this as well in the near future, once I understand how to properly go about this.
I would like pointing in the right direction to approach the issue of user login the Angular way.
app.js
angular.module('userApp', ["ngResource"]).
config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/login', {templateUrl: 'partials/login.html', controller: LoginCtrl}).
when('/loggedin', {templateUrl: 'partials/user-admin.html', controller: UserCtrl}).
otherwise({redirectTo: '/login'});
}],[ '$locationProvider', function($locationProvider) {
$locationProvider.html5Mode = true;
}]).
factory("User", function($resource) {
return $resource("users/:userId.json", {}, {
query: {method: "GET", params: {userId: "users"}, isArray: true}
});
});
controllers.js
function LoginCtrl($scope, $route, $routeParams, $location, User) {
$scope.users = User.query();
$scope.loginUser = function() {
var loggedin = false;
var totalUsers = $scope.users.length;
var usernameTyped = $scope.userUsername;
for( i=0; i < totalUsers; i++ ) {
if( $scope.users[i].name === usernameTyped ) {
loggedin = true;
break;
}
}
if( loggedin === true ) {
alert("login successful");
$location.path("/loggedin");
} else {
alert("username does not exist")
}
}
}
Yes, you are right - this is not safe. NEVER do such things:
NEVER store plain passwords in database (like "my_password_123"
NEVER return any sort of sensitive information to the client and perform secret computations in JavaScript
NEVER use simple password comparison (providedPassword == stored password) in server or client code
NEVER use unsecure (http) layer - use safe one (HTTPS) instead
A proper way of doing this is following:
Generate Hashed value of password on store it in DB. Make sure to use a strong hashing algorithm and salted passwords. At the time of writing this reply SHA-256 would be enough, but make sure to check if it's still considered safe.
Wire SSL certificate to get HTTPS support, that way noone will spy on what user sends to your server
User enters username+password and sends them to your code on the server. On the server you compute SHA-1 hash and compare it against the stored value in DB. Then you send back the RESULT of authentication to the client and keep the track of it on the server by persistent session.
Please keep in mind that most of this stuff is done by some security frameworks, like Spring Security. I wouldn't recommend doing it all from scratch, as the topic of security is vast and it's easy to make a mistake that can be used by malicious users.

Categories