
アプリケーションには認可が必要になる時があります。これは、Webサイト(またはその他のもの)で異なるアクセスレベルが異なる動作をすることを意味します。データの表示から、ユーザーグループがアクセスできない全体的なエリアまで、何でもあり得ます。
非シングルページアプリケーション(SPA)では、クレームまたはロールがデータまたはアプリケーションのエリアに関連付けられ、ユーザーがこのロールまたはクレームを持っているか持っていないかのどちらかです。SPAでも同じですが、大きな免責事項があります。SPAはブラウザにダウンロードされます。この時点で、ブラウザはコードを完全に制御できます。悪意のある人は、コードを変更して自分の思い通りにすることができます。
SPAはセキュリティ保護できないため、SPAでの認証と認可は単にユーザーエクスペリエンスです。すべての意味のあるセキュリティはWebサーバーで行う必要があります。この記事では、攻撃に対するAPIのセキュリティ保護については説明しません。サーバーテクノロジーのセキュリティに関するPluralsightのビデオを視聴するか、論文を読むことをお勧めします。
この記事の目的は、Angular 1.x SPAに認可ユーザーエクスペリエンスを追加した方法を示すことです。
セキュリティスコープ
認可が必要なUIの3つのエリアを特定しました:要素(HTML)、ルート、データです。
念のため、SPAのセキュリティ保護はサーバーのセキュリティ保護の代替にはなりません。クライアント側の権限は、単に正直な人を正直に保ち、ユーザーに良いエクスペリエンスを提供するためのものです。
3つのエリアの詳細:
要素
特定のHTML要素を非表示にする必要があります。ラベル、データを含むテーブル、ボタン、またはページ上の任意の要素である可能性があります。
ルート
ルート全体を非表示にしたい場合があります。特定のケースでは、ユーザーにビューへのアクセスを許可したくありません。ルートをセキュリティ保護することで、ユーザーはビューに移動できません。代わりに「このビューに移動する権限がありません」というメッセージが表示されます。
データ
時には、ビューの要素を非表示にするだけでは十分ではありません。賢いユーザーは、単にソースを表示してHTMLソース内の非表示データを確認したり、ブラウザにストリーミングされるのを監視したりできます。私たちが望むのは、データが最初から取得されないことです。
セキュリティの追加は複雑です。最初は、HTTP API(クライアント側)でアクセスを制限しようとしました。これがうまくいかないことをすぐに理解しました。ユーザーはデータに直接アクセスできないかもしれませんが、これは間接的にデータにアクセスできないということを意味しません。HTTP APIレイヤー(通常はアプリケーションの最下位の1つ)では、呼び出しのコンテキストを判断できないため、セキュリティの懸念を適用できません。
以下にコーディングサンプルを提供しました:
コード
認可チェックコードのサービスを作成しました。これが認可の中核です。すべての認可リクエストは、このサービスを使用して、ユーザーが特定のアクションに対して認可されているかどうかをチェックします。
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ディレクティブ
authorizeディレクティブは、特定のアクセスレベルを持たないユーザーから非表示にしたい任意のHTML要素に適用できます。ユーザーがクレームの一部としてアクセストークンを持っている場合、要素を表示することが許可されます。持っていない場合は、非表示になります。
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);
});
}
};
}]);
要素
私のアプリケーションではタブを多用しています。適切なクレームを持たないユーザーから非表示にしたいタブにauthorizeディレクティブを適用します。
<tabset>
<tab ng-cloak heading="Users" authorize="{{allowUserManagement}}">
...html content
</tab>
</tabset>
ルート
ui-router
を使用しています。残念ながら、そうでない方のために、標準のAngularJSルーターのコードは提供していません。
$stateChangeStart
でルートを認証します。そのイベントのコードは次のとおりです。
$rootScope.$on("$stateChangeStart", function(event, toState, toParams, fromState, fromParams){
AuthenticationManager.authenticate(event, toState, toParams);
});
ルートを認可する関数です。認可されている場合、ルートは続行が許可されます。認可されていない場合、ユーザーにメッセージが表示され、ホームページに誘導されます。
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();
}
}
このルーター定義では、‘authorization’というプロパティがあることに注意してください。ユーザーがこのクレームを持っている場合、続行が許可されます。
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' }
});
});
データ
場合によっては、サーバーにデータのリクエストを行いたくないことがあります。ユーザーがクレームを持っている場合、リクエストを行うことが許可されます。
記事の冒頭の上記のAuthorizationContext
は、authoriedExecution
のコードを示しています。ここでその使用法を見ることができます。
AuthorizationContext.authorizedExecution(Keys.authorization.allowUserManagement, function(){
//execute code, if the loggedin user has rights.
});
上で述べたように、これはサーバーのセキュリティ保護の代替にはなりません。このコードは、素晴らしいユーザーエクスペリエンスを提供するために機能します。
著者:Chuck Conwayはソフトウェアエンジニアリングと生成AIを専門としています。ソーシャルメディアで彼とつながりましょう:X (@chuckconway) または YouTube をご覧ください。