はまったりひらめいたり…とか…

Angularや.NETやAzureやその他色々。

AADのアプリロール情報を利用してWebAPI(ASP.NET Core)やWebアプリ(Angular)の動作を制御する

はじめに

この記事は2022/11/5時点の情報で記載されいます。

記事中のコードや情報については参照時期によっては正常に動作しない可能性がありますのでご留意ください。

コードは下記のライブラリのバージョンで動作しています。

  • Angular: ^14.2.0
  • @azure/msal-browser: ^2:30.0
  • .NET Core: 6.0

AADのアプリロールで制御を行いたい

AADを利用し社内アプリケーションなどの認証が容易になります。

認証ときたら認可が必要でADアプリに対して「APIのアクセス許可」を使うのが一般的かもしれませんが、「アプリロール」も利用することが可能です。

アプリロールはアプリケーションだけでなくユーザーやグループに対しても割り当てることが可能なので、「APIのアクセス許可」に追加する情報として利用することで下記のようなより細やかな制御を行うことが可能となります。

  • ex1)アプリケーション自体にアクセス可能だがこのページはアクセス不可
  • ex2)WebAPIリソース自体にアクセス可能だが特定WebAPIだけ実行不可

AADアプリへのロール付与とロールへのユーザー割当

AADアプリの「アプリロール」から設定を行います。

今回は許可されたメンバーの種類「ユーザーやグループ」でReaderWriterのロールを作成してみました。

次に作成したロールにユーザーやグループを割り当てます。数赤枠箇所からエンタープライズアプリケーションの画面に移動します。

「ユーザーまたはグループの追加」からユーザーを追加することができます。

この際に、先ほど作成したロールを選択できるようになっています。

認証を行うと下記のようにJWTトークンにロール情報が付与されるようになります。

WebAPIの承認の制御

WebAPIの承認時にアプリロールの検証を行うように変更します。

といっても、Authorize属性にRoles情報を付与するだけです。簡単ですね。

[Authorize(Roles = "App.Writer")]
[ApiController]
[Route("[controller]")]
public class WriterController : ControllerBase
{
    ...
}

上図のアクセストークンを利用してみます。

結果は403となりました。

ASP.NET Core WebAPIで設定したRolesをRoles = "App.Reader"に変更します。

今度は通信が成功します。返却されたトークンに含まれるアプリロール情報を参照し制御できていることが確認できます。

[Authorize(Roles = "App.Reader,App.Writer")]のように複数のロールを指定することも可能です。

Webアプリの動作制御

そこまで難しい話ではなく先程見た通りアプリロールがJWTトークン上に含まれていることが確認できました。そのアプリロールを参照し制御を入れてあげればいいだけです。

ちょっと凝ったことをしたいのでAngularを使用していますが、認証認可ライブラリのmsalはバニラでも使える@azure/msal-browserを使用していますし、他のフレームワークでもアプリロールの参照の仕方部分はそこまで変化はないと思います。

ロール情報の取得

msalのidTokenClaimsroles: string[]として格納されいます。

ただし、idTokenClaimsはObject型ですし、この情報は抜き取って利用しやすい形にしておきます。

this.client = new PublicClientApplication(environment.msalConfig);
const res = await this.client.handleRedirectPromise();
if (!res) {
  this.client.loginRedirect();
  return;
}
this._myRole = (res.idTokenClaims as { roles: string[] }).roles;

ロールによる遷移の制御

Angularの場合、CanActivateなどでルーティングに対するアクセス制御が可能です。

@Injectable({
  providedIn: 'root'
})
export class AuthGuardService implements CanActivate {

  constructor(
    private authService: AppService,
    private router: Router
  ) { }

  canActivate(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): boolean | UrlTree | Observable<boolean | UrlTree> | Promise<boolean | UrlTree> {
    const url = state.url;
    const myroles = this.authService.myRole;
    let ret = true;
    if (url.includes('reader')) {
      ret =  myroles.includes('App.Reader');
    }
    if (!ret) {
      this.router.navigate(['access-deny'])
    }
    return ret;
  }
}

雑に書くとこんな感じです。格納しているアプリロールを参照して遷移可能かどうか判定しているだけです。

アクセス不可なルートにアクセスしようとしている場合はDenyページなどに飛ばしています。

WebAPIへの通信に関しては生成されたアクセストークンにアプリロール情報が含まれていることが確認できたのでacquireTokenSilentなどを使用して取得できたアクセストークンをそのまま使えば問題ありません。

最後に

フロント、バックエンドに大きな変更やライブラリの追加をすることなく、アプリ内のロール制御が行えることが確認できました。

多くの場合はビジネスロジックに引っ張られたり、AADへのアクセス権限、管理をシステム部門などが容易にしたいという観点からロール部分の実装は自前で行う選択肢が取られることが多いかと思いますが

そこまで厳しい要件がないのであれば、このアプリロールを利用してアプリ内の権限制御を簡単に実装するのもありかと思います。

今回実験で使用したデモは下記に格納しています。

github.com

参考

アプリ ロールを追加してトークンから取得する - Microsoft Entra | Microsoft Learn