Em algum momento, uma aplicação precisa de autorização. Isso significa que diferentes níveis de acesso se comportam de forma diferente em um site (ou em qualquer outro lugar). Pode ser qualquer coisa, desde visualizar dados até áreas inteiras que não são acessíveis por um grupo de usuários.
Em aplicações não Single Page (SPA), uma claim ou role é associada a dados ou uma área da aplicação, ou o usuário tem essa role ou claim ou não tem. Em uma SPA é o mesmo, mas com um grande aviso. Uma SPA é baixada para o navegador. Neste ponto, o navegador tem controle total sobre o código. Uma pessoa mal-intencionada pode alterar o código para fazer sua vontade.
Como SPAs não podem ser protegidas, autenticação e autorização em uma SPA é simplesmente experiência do usuário. Toda segurança significativa deve ser feita no servidor web. Este artigo não cobre como proteger sua API contra ataques. Recomendo assistir a um vídeo do Pluralsight ou ler um artigo que aborde segurança para sua tecnologia de servidor.
A intenção deste artigo é mostrar como adicionei uma experiência de usuário de autorização à minha SPA Angular 1.x.
Escopos de Segurança
Identifiquei 3 áreas da UI que precisam de autorização: Elementos (HTML), Rotas e Dados.
Apenas um lembrete, proteger uma SPA não é substituto para proteger o servidor. Permissões no cliente é simplesmente para manter as pessoas honestas honestas e fornecer ao usuário uma boa experiência.
As 3 áreas em detalhes:
Elementos
Você precisará ocultar elementos HTML específicos. Pode ser um rótulo, uma tabela com dados, um botão ou qualquer elemento na página.
Rotas
Você vai querer ocultar rotas inteiras. Em certos casos, você não quer que o usuário acesse uma visualização. Ao proteger a rota, um usuário não pode navegar para a visualização. Em vez disso, será exibida uma mensagem “Você não está autorizado a navegar para esta visualização”.
Dados
Às vezes, ocultar os elementos na visualização não é suficiente. Um usuário astuto pode simplesmente visualizar a fonte e ver os dados ocultos na fonte HTML ou vê-los fluir para o navegador. O que queremos é que os dados não sejam recuperados em primeiro lugar.
Adicionar segurança é complicado. No início, tentei restringir o acesso na API HTTP (no cliente). Rapidamente percebi que isso não funcionaria. Um usuário pode não ter acesso direto aos dados, mas isso não significa que não tenha acesso indireto aos dados. Na camada da API HTTP (geralmente uma das mais baixas na aplicação), não podemos dizer o contexto da chamada e, portanto, não podemos aplicar preocupações de segurança a ela.
Abaixo forneci exemplos de código:
Código
Criei um serviço para o código de verificação de autorização. Este é o coração da autorização. Todas as solicitações de autorização usam este serviço para verificar se o usuário está autorizado para a ação específica.
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;
}
});
Diretiva Authorize
A diretiva authorize pode ser aplicada a qualquer elemento HTML que você deseja ocultar de usuários sem um nível específico de acesso. Se o usuário tiver o token de acesso como parte de suas claims, ele poderá ver o elemento. Se não tiver, fica oculto para ele.
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 muito de abas em minha aplicação. Aplico a diretiva authorize à aba que desejo ocultar de usuários sem as claims apropriadas.
<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>
Rotas
Estou usando o ui-router. Infelizmente para aqueles que não estão, não tenho código para o roteador AngularJS padrão.
No $stateChangeStart autentico a rota. Este é o código nesse evento.
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
AuthenticationManager.authenticate(event, toState, toParams);
});
A função que autoriza a rota. Se estiver autorizada, a rota pode continuar. Se não estiver autorizada, uma mensagem é exibida ao usuário e ele é direcionado para a página inicial.
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();
}
}
Nesta definição de rota você notará uma propriedade chamada ‘authorization’. Se o usuário tiver essa claim, ele poderá prosseguir.
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' }
});
});
Dados
Em alguns casos, você não quer fazer uma solicitação ao servidor para os dados. Se o usuário tiver a claim, ele poderá fazer a solicitação.
O AuthorizationContext acima no início do artigo mostra o código para authoriedExecution. Aqui você vê seu uso.
AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
//execute code, if the loggedin user has rights.
});
Como mencionei acima, isso não é substituto para proteger o servidor. Este código funciona para fornecer uma experiência de usuário maravilhosa.
Autor: Chuck Conway é um Engenheiro de IA com quase 30 anos de experiência em engenharia de software. Ele constrói sistemas de IA práticos—pipelines de conteúdo, agentes de infraestrutura e ferramentas que resolvem problemas reais—e compartilha o que está aprendendo ao longo do caminho. Conecte-se com ele nas redes sociais: X (@chuckconway) ou visite-o no YouTube e no SubStack.