🔐

Entra IDのアプリロールを利用してAzure App Serviceで権限制御を実施する

2024/11/11に公開

Azure App Service の認証と認可(Easy Auth)

Azure App Service では、Easy Auth と呼ばれる組み込みの認証機能を使ってアプリケーションに対し簡単に認証機能を提供できます。
認証の機能については多くの記事が取りあげられていますが、認可についてはあまり取り上げられていないため、本記事では認可について取り上げます。

なお本記事では、App Service の組み込み認証で Entra ID による認証を構成することを前提としていす。

Entra ID のアプリの登録とアプリロール

App Service の組み込み認証で Entra ID を利用する場合、Entra ID へのアプリの登録が行われます。

本記事で紹介する認可では、この Entra ID に登録されたアプリケーション側で設定できる「アプリロール(app roles)」を利用します。
アプリロールは、この Entra ID に登録された個別のアプリケーションに対し、カスタマイズされた独自のロールを定義するものです。
アプリロールは Entra ID のアプリケーションリソース画面の「アプリロール」から作成できます。
作成したアプリロールは、ユーザーやグループ、アプリケーションに対し付与できます。

アプリロールを利用する認可のメリットとしては、ロールの管理画面などをアプリケーション側では実装しなくて良いというものがあります。
組み込み認証もそうですが、App Service では、Azure 側で用意されている機能を利用することで、工数を抑えられるという点でも把握しておくと良い機能です。

App Service でのアプリロールの参照

アプリロールは、Easy Auth を構成した App Service では、リクエストヘッダーに含まれるX-Ms-Client-Principalをデコードすることで参照できます。

ここからは具体的なサンプルのアプリケーションを使って、作成したアプリロールを参照する手順を紹介します。

サンプルアプリケーションと付与するアプリロールの概要

サンプルアプリケーションは Python の Streamlit を使って開発します。

今回は独自のアプリロールとして「リクエストヘッダー閲覧者」というロールを作成し、ロールが付与されたユーザーがアクセスした場合に Streamlit のアプリケーション上でリクエストヘッダーの情報を表示するようにします。

アプリロールの作成

先に App Service リソースを作成し、認証の構成を済ませておきます。
App Service から新規作成の手順で認証の構成が完了している場合、Entra ID の「アプリの登録」の一覧に、App Service と同名のアプリケーションが作成されています。

このアプリケーションリソースの詳細画面に遷移し、「アプリロール」の項目から「アプリロールの作成」を押下します。

今回は以下の設定でアプリロールを作成します。

項目名
表示名 リクエストヘッダー閲覧者
許可されたメンバーの種類 ユーザーまたはグループ
RequestHeader.View
説明 リクエストヘッダーの情報を閲覧できます。
このアプリロールを有効にしますか チェック済み

作成したアプリロールのユーザーへの割り当て

作成したアプリロールをユーザーに割り当てる際は、エンタープライズアプリケーションのリソースを操作します。
アプリケーションリソースの「概要」から「エンタープライズアプリケーションに移動」を選択することでエンタープライズアプリケーションリソースの画面に遷移できます。

エンタープライズアプリケーションのリソース画面で「ユーザーとグループ」を選択し、アプリロールを割り当てたいユーザーを選択した状態で「割り当ての編集」を押下します。

ロールの割り当て画面に遷移したら、ロールの選択から先ほど作成した「リクエストヘッダー閲覧者」のロールを選択し「割り当て」を押下します。

割り当てが完了するとユーザーとグループ画面のユーザーの「割り当てられたロール」列に指定したロールの値がセットされているはずです。

以上でアプリロールの割り当てについては完了です。

X-Ms-Client-Principalのデコード

ここからはアプリケーション側でアプリロールを取得するためのコードを紹介します。

Easy Auth が構成された App Service のリクエストヘッダーのX-Ms-Client-Principalには Base64 エンコードされた JSON 形式のユーザーの要求が含まれています。
X-Ms-Client-Principalをでコードするために以下の関数を用意します。

import base64
import json


def decode_principal(x_ms_principal: str) -> dict:
    """
    X-Ms-Client-Principalの情報をデコードする

    Parameters
    ----------
    x_ms_principal : str
        X-Ms-Client-Principalの値

    Returns
    -------
    dict
        デコードされたX-Ms-Client-Principalの情報
    """
    x_ms_principal_bytes: bytes = base64.b64decode(s=x_ms_principal)
    json_str: str = x_ms_principal_bytes.decode("utf-8")
    x_ms_principal_dict: dict = json.loads(s=json_str)
    return x_ms_principal_dic

アプリロールの情報の取得

アプリロールはX-Ms-Client-Principalclaimsに含まれています。
claimsは配列形式でtypvalを含んだオブジェクト構造になっており、アプロロールが取得できる場合は以下のような形で入っていること期待されます。

[
    {"typ": "roles", "val": "RequestHeader.View"},
]

claimsからロールの値を取得するために以下の関数を用意します。

def get_roles_from_claims(claims: list) -> str:
    """
    X-Ms-Client-Principalの情報からロール情報を取得する

    Parameters
    ----------
    claims : dict
        X-Ms-Client-Principalの情報

    Returns
    -------
    str
        ユーザーに付与されたアプリロールの情報
    """
    roles: str = ""
    for claim in claims:
        if "typ" in claim and claim["typ"] == "roles" and "val" in claim:
            roles = claim["val"]
            break
    return roles

Streamlit のアプリケーション

上記の 2 つの関数を利用する Streamlit のアプリケーションを実装します。
Streamlit ではコンテキストのヘッダーから HTTP のリクエストヘッダーを参照できます。
このヘッダーの情報を関数に渡してアプリケーションを実装します。

import streamlit as st
from streamlit.logger import get_logger
from streamlit.runtime.context import StreamlitHeaders

# 定義した関数のimport
from src.app_service.auth import decode_principal, get_roles_from_claims

logger = get_logger(__name__)

headers: StreamlitHeaders = st.context.headers
headers_dict = headers.to_dict()

st.title("Azure App Service App Roles Sample")

if "X-Ms-Client-Principal" in headers_dict:
    logger.info("リクエストヘッダーのX-Ms-Client-Principalの情報を解析します")
    principal: dict = decode_principal(
        x_ms_principal=headers_dict["X-Ms-Client-Principal"]
    )
    claims: list = principal.get("claims", [])
    roles: string = get_roles_from_claims(claims=claims)

    st.header("Request Header")
    # 取得したロール情報で分岐
    if roles == "RequestHeader.View":
        # ロールがある場合はリクエストヘッダーの情報を表示
        st.json(headers.to_dict())
    else:
        # ロールがない場合はテキストを表示
        st.text("Request Headerを表示する権限がありません")

else:
    logger.info("リクエストヘッダーにX-Ms-Client-Principalの情報がありません")
    st.text("X-Ms-Client-Principalの情報がありません。")

このアプリケーションを App Service にデプロイしてアクセスをします。

先ほどの手順でアプリロールを割り当てたユーザーがアクセスする場合は、リクエストヘッダーの情報が見れます。

一方でロールが割り当てられていないうユーザーがアクセスすると以下のように見ることができません。

このような形でアプリケーション側で権限制御のロジックを実装できます。

まとめ

本記事では、App Service の組み込み認証と Entra ID 側のアプリケーションのアプリロールを使った認可について紹介しました。

アプリケーションの実装には Python の Streamlit を用いましたが、アプリケーション側でリクエストヘッダーのデコードさえできれば、どのプログラミング言語でも応用できます。

紹介したようにアプリロールを利用することで、簡単な認可の仕組みであれば少ない工数で実現できます
一方で、アプリロールはユーザーに対し、一つまでしか付与できないことやグループに対しての割り当てには Entra ID のプレミアムライセンスが必要となるなど制約となる部分も大きいため、リッチな権限管理の仕組みは実現は難しいです。

要件に応じた選択肢の一つとして持っておけると良いですね。

参考

https://learn.microsoft.com/ja-jp/azure/app-service/overview-authentication-authorization

https://learn.microsoft.com/ja-jp/azure/app-service/configure-authentication-user-identities

https://learn.microsoft.com/ja-jp/entra/identity-platform/howto-add-app-roles-in-apps

Discussion