Skip to content
Perspectivas e Iteraciones Entendiendo la IA: técnico, cotidiano y reflexiones.
← atrás

Asegurando AngularJS con Claims

14 de febrero de 2015 • 6 min de lectura

Asegurando AngularJS con Claims

En algún momento una aplicación necesita autorización. Esto significa que diferentes niveles de acceso se comportan de manera diferente en un sitio web (o cualquier cosa en realidad). Puede ser cualquier cosa desde ver datos hasta áreas completas que no son accesibles por un grupo de usuarios.

En aplicaciones que no son de Página Única (SPA), un claim o rol se asocia con datos o un área de la aplicación, ya sea que el usuario tenga este rol o claim o no lo tenga. En una SPA es lo mismo, pero con una gran advertencia. Una SPA se descarga al navegador. En este punto el navegador tiene control total sobre el código. Una persona malintencionada puede cambiar el código para hacer lo que desee.

Debido a que las SPAs no pueden ser aseguradas, la autenticación y autorización en una SPA es simplemente experiencia de usuario. Toda la seguridad significativa debe hacerse en el servidor web. Este artículo no cubre cómo asegurar tu API contra ataques. Recomiendo ver un video de Pluralsight o leer un documento que aborde la seguridad para tu tecnología de servidor.

La intención de este artículo es mostrarte cómo agregué una experiencia de usuario de autorización a mi SPA de Angular 1.x.

Ámbitos de Seguridad

He identificado 3 áreas de la UI que necesitan autorización: Elementos (HTML), Rutas, y Datos.

Solo un recordatorio, asegurar una SPA no es sustituto de asegurar el servidor. Los permisos en el cliente son simplemente para mantener a las personas honestas honestas y para proporcionar al usuario una buena experiencia.

Las 3 áreas en detalle:

Elementos

Necesitarás ocultar elementos HTML específicos. Podría ser una etiqueta, una tabla con datos, un botón, o cualquier elemento en la página.

Rutas

Querrás ocultar rutas completas. En ciertos casos no quieres que el usuario acceda a una vista. Al asegurar la ruta un usuario no puede navegar a la vista. En su lugar se les mostrará un mensaje de “No estás autorizado para navegar a esta vista”.

Datos

A veces ocultar los elementos en la vista no es suficiente. Un usuario astuto puede simplemente ver el código fuente y ver los datos ocultos en el código fuente HTML o verlos transmitirse al navegador. Lo que queremos es que los datos no se recuperen en primer lugar.

Agregar seguridad es complicado. Al principio traté de restringir el acceso en la API HTTP (en el cliente). Rápidamente me di cuenta de que esto no funcionaría. Un usuario podría no tener acceso directo a los datos, pero esto no significa que no tengan acceso indirecto a los datos. En la capa de la API HTTP (usualmente una de las más bajas en la aplicación) no podemos determinar el contexto de la llamada y por lo tanto no podemos aplicar preocupaciones de seguridad a ella.

A continuación he proporcionado ejemplos de código:

Código

Creé un servicio para el código de verificación de autorización. Este es el corazón de la autorización. Todas las solicitudes de autorización usan este servicio para verificar si el usuario está autorizado para la acción particular.

angular.module('services')
    .service('AuthorizationContext',function(_, Session){

        this.authorizedExecution = function(key, action){

            //Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
            Session.claims(function(claims){
                var claim = findKey(key, claims);

                //If Claim was found then execute the call.
                //If it was not found, do nothing
                if(claim !== undefined){
                    action();
                }
            });
        };

        this.authorized = function(key, callback){
            //Looking for the claim key that was passed in. If it exists in the claim set, then execute the action.
            Session.claims(function(claims){
                var claim = findKey(key, claims);

                //If they don't have any security key, then move forward and authorization.
                var valid = claim !== undefined;
                callback(valid);
            });
        };

        //this.agencyViewKey = '401D91E7-6EA0-46B4-9A10-530E3483CE15';

        function findKey(key, claims){
            var claim = _.find(claims, function(item){
                return item.value === key;
            });

            return claim;
        }
    });

Directiva Authorize

La directiva authorize puede aplicarse a cualquier elemento HTML que quieras ocultar de usuarios sin un nivel específico de acceso. Si el usuario tiene el token de acceso como parte de sus claims se les permite ver el elemento. Si no lo tienen, se les oculta.

angular.module(directives')
    .directive('authorize', ['$compile', 'AuthorizationContext', function($compile, AuthorizationContext) {
        return {
            restrict: 'A',
            replace: true,
            //can't have isolated the scope in a shared directive
            link:function ($scope, element, attributes) {

                var securityKey = attributes.authorize;
                AuthorizationContext.authorized(securityKey, function(authorized){
                    var el = angular.element(element);
                    el.attr('ng-show', authorized);

                    //remove the attribute, otherwise it creates an infinite loop.
                    el.removeAttr('authorize');
                    $compile(el)($scope);
                });
            }
        };
    }]);

Elementos

Dependo mucho de las pestañas en mi aplicación. Aplico la directiva authorize a la pestaña que quiero ocultar de usuarios sin los claims apropiados.

<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>

Rutas

Estoy usando el ui-router. Desafortunadamente para aquellos que no lo están, no tengo código para el router de AngularJS predeterminado.

En el $stateChangeStart autentico la ruta. Este es el código en ese evento.

$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
   AuthenticationManager.authenticate(event, toState, toParams);
});

La función que autoriza la ruta. Si está autorizada, se permite que la ruta continúe. Si no está autorizada, se muestra un mensaje al usuario y se le dirige a la página de inicio.

function authorizedRoute(toState, location, toaster, breadCrumbs){
   if(toState.authorization !== undefined){
       AuthorizationContext.authorized(toState.authorization, function(authorized){
           if(!authorized){
               toaster.pop('error', 'Error', 'You are not authorized to view this page.');
               location.path("/search");
           } else {
               breadCrumbs();
           }
       });
   } else{
       breadCrumbs();
   }
}

En esta definición de router notarás una propiedad llamada ‘authorization’. Si el usuario tiene este claim se le permite proceder.

angular.module('agency',
    [
        'ui.router',
        'services'
    ])
    .config(function config($stateProvider){
    $stateProvider.state( 'agency', {
        url: '/agency',
        controller: 'agency.index',
        templateUrl: 'agency/agency.tpl.html',
        authenticate: true,
        authorization:'401d91e7-6ea0-46b4-9a10-530e3483ce15',
        data:{ pageTitle: 'Agency' }
    });
});

Datos

En algunos casos, no quieres hacer una solicitud al servidor para los datos. Si el usuario tiene el claim se le permitirá hacer la solicitud.

El AuthorizationContext anterior al comienzo del artículo muestra el código para authoriedExecution. Aquí ves su uso.

AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
    //execute code, if the loggedin user has rights.

                });

Como mencioné anteriormente, esto no es sustituto de asegurar el servidor. Este código funciona para proporcionar una experiencia de usuario maravillosa.

↑ Volver arriba

Autor: Chuck Conway se especializa en ingeniería de software e IA Generativa. Conéctate con él en redes sociales: X (@chuckconway) o visítalo en YouTube.