Authentication with AngularJS, session management and security issues with REST Api WS

戏子无情 提交于 2019-11-28 02:46:18
Kos Prov

If you talk to the server via https, you don't have a problem with replay attacks.

My suggestion would be to leverage your server's security technology. For example, JavaEE has an out-of-the-box login mechanism, declarative role-based protection of resources (your REST endpoints) etc. These are all managed with a set of cookies and you don't have to care about storage and expiration. Check out what your server/framework already gives you.

If you plan to expose your API to a broader audience (not specifically to the browser-based UI that you serve) or other types of clients (e.g. mobile app), consider adopting OAuth.

Off the top of my head, Angular has the following security features (will add more as they pop-out):

CSRF/XSRF attacks

Angular supports an out of the box mechanism for CSRF protection. Check out $http docs. Server-side support is needed.

Content Security Policy

Angular has a mode of expression evaluation that is compatible with more strict JavaScript runtimes that are enforced when CSP is enabled. Check out ng-csp docs.

Strict Contextual Escaping

Use Angular's new $sce feature (1.2+) to harden you UI against XSS attacks etc. It's a bit less convenient but more secure. Check out the docs here.

This is client side security which you can implement in regular Angular versions. I have tried and tested this. (Please find my article here:- https://www.intellewings.com/post/authorizationonangularroutes ) In addition to client side route security, you need to secure access at server side also. Client side security helps in avoiding extra round trip to server. However, if someone tricks the browser , then server server side security should be able to reject unauthorized access.

Hope this helps!

Step 1: Define Global variables in app-module

-define roles for the application

  var roles = {
        superUser: 0,
        admin: 1,
        user: 2
    };

-Define route For Unauthorized Access for the application

 var routeForUnauthorizedAccess = '/SomeAngularRouteForUnauthorizedAccess';

Step 2: Define the service for authorization

appModule.factory('authorizationService', function ($resource, $q, $rootScope, $location) {
    return {
    // We would cache the permission for the session, to avoid roundtrip to server for subsequent requests
    permissionModel: { permission: {}, isPermissionLoaded: false  },

    permissionCheck: function (roleCollection) {
    // we will return a promise .
            var deferred = $q.defer();

    //this is just to keep a pointer to parent scope from within promise scope.
            var parentPointer = this;

    //Checking if permisison object(list of roles for logged in user) is already filled from service
            if (this.permissionModel.isPermissionLoaded) {

    //Check if the current user has required role to access the route
                    this.getPermission(this.permissionModel, roleCollection, deferred);
} else {
    //if permission is not obtained yet, we will get it from  server.
    // 'api/permissionService' is the path of server web service , used for this example.

                    $resource('/api/permissionService').get().$promise.then(function (response) {
    //when server service responds then we will fill the permission object
                    parentPointer.permissionModel.permission = response;

    //Indicator is set to true that permission object is filled and can be re-used for subsequent route request for the session of the user
                    parentPointer.permissionModel.isPermissionLoaded = true;

    //Check if the current user has required role to access the route
                    parentPointer.getPermission(parentPointer.permissionModel, roleCollection, deferred);
}
                );
}
            return deferred.promise;
},

        //Method to check if the current user has required role to access the route
        //'permissionModel' has permission information obtained from server for current user
        //'roleCollection' is the list of roles which are authorized to access route
        //'deferred' is the object through which we shall resolve promise
    getPermission: function (permissionModel, roleCollection, deferred) {
        var ifPermissionPassed = false;

        angular.forEach(roleCollection, function (role) {
            switch (role) {
                case roles.superUser:
                    if (permissionModel.permission.isSuperUser) {
                        ifPermissionPassed = true;
                    }
                    break;
                case roles.admin:
                    if (permissionModel.permission.isAdministrator) {
                        ifPermissionPassed = true;
                    }
                    break;
                case roles.user:
                    if (permissionModel.permission.isUser) {
                        ifPermissionPassed = true;
                    }
                    break;
                default:
                    ifPermissionPassed = false;
            }
        });
        if (!ifPermissionPassed) {
            //If user does not have required access, we will route the user to unauthorized access page
            $location.path(routeForUnauthorizedAccess);
            //As there could be some delay when location change event happens, we will keep a watch on $locationChangeSuccess event
            // and would resolve promise when this event occurs.
            $rootScope.$on('$locationChangeSuccess', function (next, current) {
                deferred.resolve();
            });
        } else {
            deferred.resolve();
        }
    }

};
});

Step 3: Use security in routing: Lets use use all our hardword done so far, to secure the routes

var appModule = angular.module("appModule", ['ngRoute', 'ngResource'])
    .config(function ($routeProvider, $locationProvider) {
        $routeProvider
            .when('/superUserSpecificRoute', {
                templateUrl: '/templates/superUser.html',//path of the view/template of route
                caseInsensitiveMatch: true,
                controller: 'superUserController',//angular controller which would be used for the route
                resolve: {//Here we would use all the hardwork we have done above and make call to the authorization Service 
                    //resolve is a great feature in angular, which ensures that a route controller(in this case superUserController ) is invoked for a route only after the promises mentioned under it are resolved.
                    permission: function(authorizationService, $route) {
                        return authorizationService.permissionCheck([roles.superUser]);
                    },
                }
            })
        .when('/userSpecificRoute', {
            templateUrl: '/templates/user.html',
            caseInsensitiveMatch: true,
            controller: 'userController',
            resolve: {
                permission: function (authorizationService, $route) {
                    return authorizationService.permissionCheck([roles.user]);
                },
            }
           })
             .when('/adminSpecificRoute', {
                 templateUrl: '/templates/admin.html',
                 caseInsensitiveMatch: true,
                 controller: 'adminController',
                 resolve: {
                     permission: function(authorizationService, $route) {
                         return authorizationService.permissionCheck([roles.admin]);
                     },
                 }
             })
             .when('/adminSuperUserSpecificRoute', {
                 templateUrl: '/templates/adminSuperUser.html',
                 caseInsensitiveMatch: true,
                 controller: 'adminSuperUserController',
                 resolve: {
                     permission: function(authorizationService, $route) {
                         return authorizationService.permissionCheck([roles.admin,roles.superUser]);
                     },
                 }
             })
    });
app/js/app.js
-------------

'use strict';
// Declare app level module which depends on filters, and services
var app= angular.module('myApp', ['ngRoute']);
app.config(['$routeProvider', function($routeProvider) {
  $routeProvider.when('/login', {templateUrl: 'partials/login.html', controller: 'loginCtrl'});
  $routeProvider.when('/home', {templateUrl: 'partials/home.html', controller: 'homeCtrl'});
  $routeProvider.otherwise({redirectTo: '/login'});
}]);


app.run(function($rootScope, $location, loginService){
    var routespermission=['/home'];  //route that require login
    $rootScope.$on('$routeChangeStart', function(){
        if( routespermission.indexOf($location.path()) !=-1)
        {
            var connected=loginService.islogged();
            connected.then(function(msg){
                if(!msg.data) $location.path('/login');
            });
        }
    });
});

 app/js/controller/loginCtrl.js
-------------------------------

'use strict';

app.controller('loginCtrl', ['$scope','loginService', function ($scope,loginService) {
    $scope.msgtxt='';
    $scope.login=function(data){
        loginService.login(data,$scope); //call login service
    };
}]);

app/js/directives/loginDrc.js
-----------------------------
'use strict';
app.directive('loginDirective',function(){
    return{
        templateUrl:'partials/tpl/login.tpl.html'
    }

});
app/js/services/sessionService.js
---------------------------------
'use strict';

app.factory('sessionService', ['$http', function($http){
    return{
        set:function(key,value){
            return sessionStorage.setItem(key,value);
        },
        get:function(key){
            return sessionStorage.getItem(key);
        },
        destroy:function(key){
            $http.post('data/destroy_session.php');
            return sessionStorage.removeItem(key);
        }
    };
}])

app/js/services/loginService
----------------------------
'use strict';
app.factory('loginService',function($http, $location, sessionService){
    return{
        login:function(data,scope){
            var $promise=$http.post('data/user.php',data); //send data to user.php
            $promise.then(function(msg){
                var uid=msg.data;
                if(uid){
                    //scope.msgtxt='Correct information';
                    sessionService.set('uid',uid);
                    $location.path('/home');
                }          
                else  {
                    scope.msgtxt='incorrect information';
                    $location.path('/login');
                }                  
            });
        },
        logout:function(){
            sessionService.destroy('uid');
            $location.path('/login');
        },
        islogged:function(){
            var $checkSessionServer=$http.post('data/check_session.php');
            return $checkSessionServer;
            /*
            if(sessionService.get('user')) return true;
            else return false;
            */
        }
    }

});

index.html
----------
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
  <meta charset="utf-8">
  <title>My AngularJS App</title>
  <link rel="stylesheet" href="css/app.css"/>
</head>
<body>
  <div ng-view></div>
  <!-- In production use:
  <script src="//ajax.googleapis.com/ajax/libs/angularjs/1.0.7/angular.min.js"></script>
  -->
  <script src="lib/angular/angular.js"></script>
  <script src="lib/angular/angular-route.js"></script>

  <script src="js/app.js"></script>

  <script src="js/directives/loginDrc.js"></script>

  <script src="js/controllers/loginCtrl.js"></script>
  <script src="js/controllers/homeCtrl.js"></script>

  <script src="js/services/loginService.js"></script>
  <script src="js/services/sessionService.js"></script>
</body>
</html>

First, there is no short or only one answer to what you have asked. In addition to what has already been answered, let me try to add something more. At enterprise level , there are four major components ,

  1. UI
  2. User Authentication Server - Here you validate user credentials and generate necessary cookies for user to move forward on UI. If this step fails, user gets stopped right there. This server has nothing to do with API token generation & you need this for non - API based systems too.Google Authentication is one example.

Extension:Siteminder Authentication

SiteMinder Cookies, their Usage, Contents and Security

Building a Java authentication server for Chatkit

  1. API Token Server - This server generates API tokens based on cookies generated on step # 2 i.e. you send cookies to server and get a token
  2. APIs - You use token generated in step # 3 to make API calls.

Its better that you deploy & manage these four components independently for better scale . e.g. in this article, they have mixed up authentication & token generation in single end point & thats not good - Microservices with Spring Boot — Authentication with JWT (Part 3)

By your write up, it looks that you have written component two & three on your own - usually folks utilize some ready made tools for this like CA SiteMinder - How CA Siteminder works – Basics

Any tips on how to generate a unique strong token?

I would suggest that you go via standardized way for better maintainability & security i.e. you choose JWT format. JSON Web Token (JWT) Authentication Scheme

Your token will be signed & encrypted so You would also need an encryption key server & a mechanism to rotate these keys at regular intervals.

JSON Web Tokens - How to securely store the key?

What is the difference between JWT and encrypting some json manually with AES?

CA person has attached a detailed pdf guide on this community portal - that will help you to understand the overall flow.

Sample Code / App to use of REST JWT token API

Your API code will need to fetch the encryption key and decrypt & decode the token to authenticate token. If token is tampered or missing, you need to flag it as such. There are libraries available for this.

Is it better to store the token inside a new cookie or in localStorage?

local Storage if UI & API are on different domains & Cookies if on same domain.

Should JWT be stored in localStorage or cookie?

Cross-Domain Cookies

Security of an application is also dependent on deployment model and that part you haven't specified in your question. Sometimes , developers might leave as simple flaws in their code as SQL Injection :)

What if JWT is stolen?

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!