
På et tidspunkt trenger en applikasjon autorisasjon. Dette betyr at forskjellige tilgangsnivåer oppfører seg forskjellig på en nettside (eller hva som helst for den saks skyld). Det kan være alt fra å se data til hele områder som ikke er tilgjengelige for en gruppe brukere.
I ikke-Single Page Applications (SPA), er en claim eller rolle assosiert med data eller et område av applikasjonen, enten har brukeren denne rollen eller claim’en eller så har han ikke det. I en SPA er det det samme, men med en stor ansvarsfraskrivelse. En SPA lastes ned til nettleseren. På dette punktet har nettleseren total kontroll over koden. En ondsinnet person kan endre koden til å gjøre hans vilje.
Fordi SPA-er ikke kan sikres, er autentisering og autorisasjon i en SPA ganske enkelt brukeropplevelse. All meningsfull sikkerhet må gjøres på webserveren. Denne artikkelen dekker ikke sikring av ditt API mot angrep. Jeg anbefaler å se en video fra Pluralsight eller lese et dokument som adresserer sikkerhet for din serverteknologi.
Hensikten med denne artikkelen er å vise deg hvordan jeg la til en autorisasjonsbrukeropplevelse til min Angular 1.x SPA.
Sikkerhetsområder
Jeg har identifisert 3 områder av brukergrensesnittet som trenger autorisasjon: Elementer (HTML), Ruter, og Data.
Bare en påminnelse, sikring av en SPA er ingen erstatning for sikring av serveren. Tillatelser på klienten er ganske enkelt for å holde de ærlige menneskene ærlige og for å gi brukeren en god opplevelse.
De 3 områdene i detalj:
Elementer
Du må skjule spesifikke HTML-elementer. Det kan være en etikett, en tabell med data, en knapp, eller hvilket som helst element på siden.
Ruter
Du vil skjule hele ruter. I visse tilfeller vil du ikke at brukeren skal få tilgang til en visning. Ved å sikre ruten kan en bruker ikke navigere til visningen. De vil i stedet få vist en “Du er ikke autorisert til å navigere til denne visningen” melding.
Data
Noen ganger er det ikke nok å skjule elementene i visningen. En oppvakt bruker kan ganske enkelt se kilden og se de skjulte dataene i HTML-kilden eller se dem strømme til nettleseren. Det vi ønsker er at dataene ikke skal hentes i utgangspunktet.
Å legge til sikkerhet er vanskelig. Først prøvde jeg å begrense tilgangen ved HTTP API (på klienten). Jeg innså raskt at dette ikke ville fungere. En bruker har kanskje ikke direkte tilgang til dataene, men dette betyr ikke at de ikke har indirekte tilgang til dataene. På HTTP API-laget (vanligvis et av de laveste i applikasjonen) kan vi ikke fortelle konteksten til kallet og kan derfor ikke anvende sikkerhetshensyn på det.
Nedenfor har jeg gitt kodingseksempler:
Kode
Jeg opprettet en tjeneste for autorisasjonssjekkkoden. Dette er hjertet av autorisasjonen. Alle autorisasjonsforespørsler bruker denne tjenesten for å sjekke om brukeren er autorisert for den spesielle handlingen.
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;
}
});
Authorize Direktiv
Authorize direktivet kan anvendes på hvilket som helst HTML-element som du ønsker å skjule fra brukere uten et spesifikt tilgangsnivå. Hvis brukeren har tilgangstokenet som en del av deres claims får de lov til å se elementet. Hvis de ikke har det er det skjult fra dem.
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);
});
}
};
}]);
Elementer
Jeg er sterkt avhengig av faner i applikasjonen min. Jeg anvender authorize direktivet på fanen som jeg ønsker å skjule fra brukere uten de riktige claims.
<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>
Ruter
Jeg bruker ui-router
. Dessverre for de som ikke gjør det, har jeg ikke kode for den innebygde AngularJS-ruteren.
I $stateChangeStart
autentiserer jeg ruten. Dette er koden i den hendelsen.
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
AuthenticationManager.authenticate(event, toState, toParams);
});
Funksjonen som autoriserer ruten. Hvis den er autorisert, får ruten lov til å fortsette. Hvis den ikke er autorisert, vises en melding til brukeren og de blir dirigert til hjemmesiden.
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();
}
}
I denne ruterdefinisjonen vil du legge merke til en egenskap kalt ‘authorization’. Hvis brukeren har denne claim’en får de lov til å fortsette.
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' }
});
});
Data
I noen tilfeller ønsker du ikke å gjøre en forespørsel til serveren for dataene. Hvis brukeren har claim’en får de lov til å gjøre forespørselen.
AuthorizationContext
ovenfor i begynnelsen av artikkelen viser koden for authoriedExecution
. Her ser du bruken av den.
AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
//execute code, if the loggedin user has rights.
});
Som jeg nevnte ovenfor, er dette ingen erstatning for sikring av serveren. Denne koden fungerer for å gi en fantastisk brukeropplevelse.
Forfatter: Chuck Conway spesialiserer seg på programvareutvikling og Generativ AI. Koble til ham på sosiale medier: X (@chuckconway) eller besøk ham på YouTube.