I have a angularJS application which is using angular ui-router for routing.
I added the following level of security but that doesn't helped me out:
I am storing token and user role in local storage.
on the basis of user roles i'm redirecting user to routes.
I have applied watch service on local storage but on refresh i
didn't get last(old) value of local storage so in this case my check(watch) fails.
when someone changes token at local storage and refresh site.
then i'm not able to check whether token is changed or not.
In this case user is able to view template but not able to fetch data cause i have added security layer at the backend. but i need to prevent access to template also.
Please help me add security at angular (client) to overcome these issues.
If i understand correctly, you are looking for a way to prevent the user from seeing the template if they do not have the required permissions. What i did is, when changing pages, first check to see if the user has the correct permission. In each state, i add the permission required to get to the page.
//these are the permissions the user currently has
var currentPermissions = {
"User.Read":true,
"User.Create":false
}
var app = angular.module('myApp', ['ui.router']);
app.config( function($stateProvider) {
$stateProvider.state('page1', {
url: "/page1",
data: {permission: "User.Read"}, //the perm required
template: "Page 1"
})
.state('page2', {
url: "/page2",
data: {permission: "User.Read"},
template: "Page 2"
})
.state('page3', {
url: "/page3",
data: {permission: "User.Create"},
template: "Page 3"
})
.state("noAccess", {
url:"/noAccess",
template: "access denied"
});
});
app.run(function($rootScope, $state) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){
//check to make sure user has correct perm
if(toState.data && !currentPermissions[toState.data.permission]){
event.preventDefault();
$state.go('noAccess');//redirect if user does not have perm
}
});
});
Fiddle at: https://jsfiddle.net/Darin_Cardin/mwg33rm4/
Related
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.
So I built an SPA for the company I work for and it can't go online until I have incorporated authentication due to the VERY sensitive nature of the data it displays. For this reason, I am attempting to build a static express page for login as opposed to handling this in the SPA.
Currently I'm doing it like this:
Serve my login page.
Call the API route used for authentication.
If successful serve the SPA.
If unsuccessful display an appropriate error message and re-render the login page.
I have built an authentication scheme with JWTs before, so I am not worried about that part. Right now I simply want to serve my SPA with the click of a button. If I remove the routes below and go to the home page my applications loads as normal. My problem is I'm trying to route to the angular app's homepage ('/#/') and express renders the login page.
The button on my homepage makes a get request to login. I want this to redirect to the SPA I've already built. Seems like it should be simple but I can't figure out how to do it for the life of me.
//Login routes, success delivers angular SPA
app.get('/', function (req, res) {
res.render('index.jade', {
title : "n562d",
strapline : "Please Log In"
})
});
app.get('/login', function (req, res) {
res.redirect('/#/')
});
I think you are bit confused. Use express as the REST engine when it comes to routes. Angular routes will take care of the display logic and view routing on the client side.
I would suggest you to pass JSON data to front end angular and let it do the job for you.
For example:
app.get('/', function (req, res) {
res.json({
title : "n562d",
strapline : "Please Log In"
})
});
You can access the API at the endpoint: http://localhost:3000/
Use $resource services to access the express endpoint:
example:
var MyResource = $resource('/');
var myResource = new MyResource();
myResource.$get(function(result){
//result holds -->{title : "n562d", strapline : "Please Log In"}
//use $location to change the uri, which is handled by Angular route config
$location.path('/')
});
For angular routing,i would suggest you to use ui-router
example:
function($stateProvider,$urlRouterProvider){
$urlRouterProvider.otherwise("/");
$stateProvider
.state('index', {
url: "/",
templateUrl: 'app/authorization/index.tpl.html',
controller: 'AuthController'
})
.state('login', {
url: "/login/",
templateUrl: 'app/authorization/login.tpl.html',
controller: 'AuthController'
})
.state('signup',{
url: "/signup/",
templateUrl : 'app/authorization/signup.tpl.html',
controller: 'AuthController'
});
}
]);
Please note: I just named my controller as AuthController, you can replace it with name of your controller, same with views to.
Let me know if you need more detailed answer then i will update it. I hope it helps.
In my AngularJS application I want to catch errors and send the user to a 404 state like so:
$rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams){
//console.log(unfoundState.to);
//console.log(unfoundState.toParams);
//console.log(unfoundState.options);
$state.go('404');
});
This is for when a user clicks a bad link (e.g. has no state, as ones with states but no content are handled by the otherwise method) in the app, etc.
The code loads the 404 state fine:
.state('404',
{
views: {
'body': {
templateUrl: 'partials/404.html',
}
}
});
But what I want to do is change the URL to the state they tried to access, so basically load the 404 state but use the incorrect URL (as a 404 would work in a server environment).
I've looked into doing:
history.replaceState(null, null, unfoundState.to);
$state.go('404');
But that causes major errors in the app and changes the URL but not the state!
How can I do this?
Try this to catch all URLs not matching any of your previous routes :
.state("404", {
url: "*path", // will catch all URLs
templateUrl: 'partials/404.html'
})
According to the docs, I think you can do:
$state.go('404',{},{location:false});
Note: I'm in the process of creating a plunkr to verify but I don't have time to finish it up completely right now
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.
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.