Microsoft Entra (ME-ID) グループ、管理者ロール、およびアプリ ロール

この記事では、Microsoft Entra ID (ME-ID) のグループとロールを使用するように Blazor WebAssembly を構成する方法を説明します。

ME-ID には、ASP.NET Core Identity と組み合わせることができる承認方法がいくつか用意されています。

  • グループ
    • セキュリティ
    • Microsoft 365
    • 配布
  • 役割
    • ME-ID 組み込み管理者ロール
    • アプリ ロール

この記事のガイダンスは、次のトピックで説明されている Blazor WebAssembly ME-ID デプロイ シナリオに適用されます。

この記事の例では、新しい .NET/C# の機能を利用します。 この例に .NET 7 以前を使用する場合は、若干の変更が必要です。 ただし、ME-ID と Microsoft Graph の操作に関連するテキストとコードの例は、ASP.NET Core のすべてのバージョンで同じです。

サンプル アプリ

次のリンクを使用して、リポジトリのルートから最新バージョンのフォルダーを介して BlazorWebAssemblyEntraGroupsAndRoles という名前のサンプル アプリにアクセスします。 サンプルは .NET 8 以降向けに提供されています。 アプリの実行手順については、サンプル アプリの README ファイルをご覧ください。

サンプル アプリには、ユーザーの要求を表示するための UserClaims コンポーネントが含まれています。 UserData コンポーネントには、ユーザーの基本的なアカウント プロパティが表示されます。

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

前提条件

この記事のガイダンスでは、「ASP.NET Core Blazor WebAssembly で Graph API を使用する」の Graph SDK のガイダンスに従って Microsoft Graph API を実装します。 Graph SDK の実装ガイダンスに従ってアプリを構成し、アプリがテスト ユーザー アカウントの Graph API データを取得できることをテストして確認します。 さらに、Graph API 記事のセキュリティに関する記事のクロスリンクを参照して、Microsoft Graph のセキュリティの概念を確認してください。

Graph SDK をローカル環境でテストする場合は、残っている Cookie がテストに干渉しないよう、テストのたびに新しいプライベートまたはシークレット ブラウザー セッションを使うことをお勧めします。 詳しくは、「Microsoft Entra ID を使用して、ASP.NET Core Blazor WebAssembly スタンドアロン アプリをセキュリティで保護する」をご覧ください。

ME-ID アプリ登録オンライン ツール

この記事では、アプリの ME-ID アプリ登録を構成するように求める際に、全体を通して Azure portal を参照していますが、Microsoft Entra 管理センターも ME-ID アプリ登録を管理するための実行可能なオプションです。 どちらのインターフェイスも使用できますが、この記事のガイダンスでは、特に Azure portal のジェスチャについて説明します。

スコープ

アクセス許可スコープは、同じことを意味し、セキュリティ ドキュメントと Azure portal で意味の区別なく使用されます。 この記事では、本文が Azure portal を参照していない限り、Graph のアクセス許可を参照するときにスコープ/を使用します。

スコープでは大文字と小文字が区別されないため、User.Readuser.read と同じです。 どちらの形式でも自由に使用できますが、アプリケーション コード間で一貫した選択肢をお勧めします。

ユーザー プロファイル、ロールの割り当て、グループ メンバーシップ データに対する Microsoft Graph API 呼び出しを許可するには、Azure portal にて、アプリを委任User.Readスコープ (https://graph.microsoft.com/User.Read) で構成します。ユーザー データの読み取りアクセスは、個々のユーザーに付与 (委任) されたスコープによって決まるためです。 このスコープは、前にリストした記事 (Microsoft アカウントを使用したスタンドアロンまたは ME-ID を使用したスタンドアロン) で説明されている ME-ID デプロイ シナリオで必要なスコープに加えて必要です。

その他の必要なスコープは次のとおりです。

  • 委任RoleManagement.Read.Directoryスコープ (https://graph.microsoft.com/RoleManagement.Read.Directory): サインインしているユーザーの代わりに、アプリが会社のディレクトリのロールベースのアクセス制御 (RBAC) 設定を読み取ることができます。 これには、ディレクトリ ロール テンプレート、ディレクトリ ロール、メンバーシップの読み取りが含まれます。 ディレクトリ ロールのメンバーシップは、ME-ID 組み込み管理者ロールのアプリで directoryRole 要求を作成するために使用されます。 管理者の同意が必要です。
  • 委任AdministrativeUnit.Read.Allスコープ (https://graph.microsoft.com/AdministrativeUnit.Read.All): サインインしているユーザーの代わりに、アプリが管理単位と管理単位のメンバーシップを読み取ることができます。 これらのメンバーシップは、アプリで administrativeUnit 要求を作成するために使用されます。 管理者の同意が必要です。

詳細については、「Microsoft identity プラットフォームでのアクセス許可と同意の概要」および「Microsoft Graph のアクセス許可の概要」をご覧ください。

カスタム ユーザー アカウント

Azure portal で ME-ID セキュリティ グループと ME-ID 管理者ロールにユーザーを割り当てます。

この記事の例では:

  • サーバー API データへのアクセスを認可するために、ユーザーが Azure portal ME-ID テナントの ME-ID 課金管理者ロールに割り当てられているとします。
  • 認可ポリシーを使用して、アプリ内でアクセスを制御します。

RemoteUserAccount を拡張して、次のプロパティを含めます。

CustomUserAccount.cs:

using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

namespace BlazorWebAssemblyEntraGroupsAndRoles;

public class CustomUserAccount : RemoteUserAccount
{
    [JsonPropertyName("roles")]
    public List<string>? Roles { get; set; }

    [JsonPropertyName("oid")]
    public string? Oid { get; set; }
}

Microsoft.Graph のアプリにパッケージ参照を追加します。

Note

.NET アプリへのパッケージの追加に関するガイダンスについては、「パッケージ利用のワークフロー」 (NuGet ドキュメント) の "パッケージのインストールと管理" に関する記事を参照してください。 NuGet.org で正しいパッケージ バージョンを確認します。

記事「ASP.NET Core Blazor WebAssembly で Graph API を使用する」の Graph SDK のガイダンスにある Graph SDK ユーティリティ クラスと構成を追加します。 記事の例 wwwroot/appsettings.json ファイルに示されているように、アクセス トークンの User.ReadRoleManagement.Read.DirectoryAdministrativeUnit.Read.All スコープを指定します。

次のカスタム ユーザー アカウント ファクトリをアプリに追加します。 カスタム ユーザー ファクトリは、以下を確立するために使用されます。

  • アプリ ロールの要求 (role) (「アプリ ロール」セクションで説明します)。

  • ユーザーの携帯電話番号 (mobilePhone) と勤務先所在地 (officeLocation) に対するユーザー プロファイル データ要求の例。

  • ME-ID 管理者ロールの要求 (directoryRole)。

  • ME-ID 管理単位要求 (administrativeUnit)。

  • ME-ID グループ要求 (directoryGroup)。

  • ILogger (logger) (情報やエラーをログする必要がある場合に便利なように)。

CustomAccountFactory.cs:

using System.Security.Claims;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication.Internal;
using Microsoft.Graph;
using Microsoft.Kiota.Abstractions.Authentication;

namespace BlazorWebAssemblyEntraGroupsAndRoles;

public class CustomAccountFactory(IAccessTokenProviderAccessor accessor,
        IServiceProvider serviceProvider, ILogger<CustomAccountFactory> logger,
        IConfiguration config)
    : AccountClaimsPrincipalFactory<CustomUserAccount>(accessor)
{
    private readonly ILogger<CustomAccountFactory> logger = logger;
    private readonly IServiceProvider serviceProvider = serviceProvider;
    private readonly string? baseUrl = string.Join("/",
        config.GetSection("MicrosoftGraph")["BaseUrl"],
        config.GetSection("MicrosoftGraph")["Version"]);

    public override async ValueTask<ClaimsPrincipal> CreateUserAsync(
        CustomUserAccount account,
        RemoteAuthenticationUserOptions options)
    {
        var initialUser = await base.CreateUserAsync(account, options);

        if (initialUser.Identity is not null &&
            initialUser.Identity.IsAuthenticated)
        {
            var userIdentity = initialUser.Identity as ClaimsIdentity;

            if (userIdentity is not null && !string.IsNullOrEmpty(baseUrl) &&
                account.Oid is not null)
            {
                account?.Roles?.ForEach((role) =>
                {
                    userIdentity.AddClaim(new Claim("role", role));
                });

                try
                {
                    var client = new GraphServiceClient(
                        new HttpClient(),
                        serviceProvider
                            .GetRequiredService<IAuthenticationProvider>(),
                        baseUrl);

                    var user = await client.Me.GetAsync();

                    if (user is not null)
                    {
                        userIdentity.AddClaim(new Claim("mobilephone",
                            user.MobilePhone ?? "(000) 000-0000"));
                        userIdentity.AddClaim(new Claim("officelocation",
                            user.OfficeLocation ?? "Not set"));
                    }

                    var memberOf = client.Users[account?.Oid].MemberOf;

                    var graphDirectoryRoles = await memberOf.GraphDirectoryRole.GetAsync();

                    if (graphDirectoryRoles?.Value is not null)
                    {
                        foreach (var entry in graphDirectoryRoles.Value)
                        {
                            if (entry.RoleTemplateId is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("directoryRole", entry.RoleTemplateId));
                            }
                        }
                    }

                    var graphAdministrativeUnits = await memberOf.GraphAdministrativeUnit.GetAsync();

                    if (graphAdministrativeUnits?.Value is not null)
                    {
                        foreach (var entry in graphAdministrativeUnits.Value)
                        {
                            if (entry.Id is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("administrativeUnit", entry.Id));
                            }
                        }
                    }

                    var graphGroups = await memberOf.GraphGroup.GetAsync();

                    if (graphGroups?.Value is not null)
                    {
                        foreach (var entry in graphGroups.Value)
                        {
                            if (entry.Id is not null)
                            {
                                userIdentity.AddClaim(
                                    new Claim("directoryGroup", entry.Id));
                            }
                        }
                    }
                }
                catch (AccessTokenNotAvailableException exception)
                {
                    exception.Redirect();
                }
            }
        }

        return initialUser;
    }
}

上記のコードでは次の操作が行われます。

  • 推移的なメンバーシップは含まれていません。 アプリで直接的および推移的なグループ メンバーシップの要求が必要である場合は、MemberOf プロパティ (IUserMemberOfCollectionWithReferencesRequestBuilder) を TransitiveMemberOf (IUserTransitiveMemberOfCollectionWithReferencesRequestBuilder) に置き換えます。
  • directoryRole 要求内の GUID 値を、ME-ID 管理者ロール テンプレート ID (Microsoft.Graph.Models.DirectoryRole.RoleTemplateId) に設定します。 テンプレート ID は、アプリでユーザー承認ポリシーを作成するための安定した識別子です。これについては、この記事の後半で説明します。 ディレクトリ ロール要求値はテナント間で安定していないため、ディレクトリ ロール要求値には entry.Id を使用しないでください。

次に、カスタム ユーザー アカウント ファクトリを使うように MSAL 認証を構成します。

Program ファイルで Microsoft.AspNetCore.Components.WebAssembly.Authentication 名前空間が使われていることを確認します。

using Microsoft.AspNetCore.Components.WebAssembly.Authentication;

AddMsalAuthentication 呼び出しを次のように更新します。 Blazor フレームワークの RemoteUserAccount は、MSAL 認証とアカウント要求プリンシパル ファクトリに対してアプリの CustomUserAccount に置き換えられていることに注意してください。

builder.Services.AddMsalAuthentication<RemoteAuthenticationState,
    CustomUserAccount>(options =>
    {
        builder.Configuration.Bind("AzureAd",
            options.ProviderOptions.Authentication);
        options.UserOptions.RoleClaim = "role";
    })
    .AddAccountClaimsPrincipalFactory<RemoteAuthenticationState, CustomUserAccount,
        CustomAccountFactory>();

ASP.NET Core で Graph API を使用するBlazor WebAssembly」の記事で説明されている Program ファイルに、Graph SDK コードが存在する場合を確認します。

var baseUrl =
    string.Join("/",
        builder.Configuration.GetSection("MicrosoftGraph")["BaseUrl"] ??
            "https://graph.microsoft.com",
        builder.Configuration.GetSection("MicrosoftGraph")["Version"] ??
            "v1.0");
var scopes = builder.Configuration.GetSection("MicrosoftGraph:Scopes")
    .Get<List<string>>() ?? [ "user.read" ];

builder.Services.AddGraphClient(baseUrl, scopes);

重要

Azure portal でアプリの登録で、次のアクセス許可が付与されていることを確認します。

  • User.Read
  • RoleManagement.Read.Directory(管理者の同意が必要)
  • AdministrativeUnit.Read.All(管理者の同意が必要)

Graph SDK ガイダンスに従って、wwwroot/appsettings.json 構成が正しいことを確認します。

wwwroot/appsettings.json:

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/{TENANT ID}",
    "ClientId": "{CLIENT ID}",
    "ValidateAuthority": true
  },
  "MicrosoftGraph": {
    "BaseUrl": "https://graph.microsoft.com",
    "Version": "v1.0",
    "Scopes": [
      "User.Read",
      "RoleManagement.Read.Directory",
      "AdministrativeUnit.Read.All"
    ]
  }
}

Azure portal でアプリの ME-ID 登録から、次のプレースホルダーの値を指定します。

  • {TENANT ID}: ディレクトリ (テナント) ID GUID の値。
  • {CLIENT ID}: アプリケーション (クライアント) ID の GUID 値。

承認の構成

Program ファイルで、アプリ ロール (ロール名別)、ME-ID 組み込み管理者ロール (ロール テンプレート ID/GUID 別)、またはセキュリティ グループ (オブジェクト ID/GUID 別) ごとに、ポリシーを作成します。 次の例では、ME-ID の組み込み課金管理者ロールに対するポリシーを作成します。

builder.Services.AddAuthorizationCore(options =>
{
    options.AddPolicy("BillingAdministrator", policy => 
        policy.RequireClaim("directoryRole", 
            "b0f54661-2d74-4c50-afa3-1ec803f12efe"));
});

ME-ID 管理者ロールの ID (GUID) の完全な一覧については、ME-ID ドキュメントの「ロール テンプレート ID」をご覧ください。 Azure セキュリティまたは O365 グループ ID (GUID) については、アプリの登録の Azure portal グループ ペインのグループの オブジェクト ID をご覧ください。 承認ポリシーの詳細については、「ASP.NET Core でのポリシー ベースの認可」を参照してください。

次の例のアプリでは、前述のポリシーを使用してユーザーを承認します。

AuthorizeView コンポーネントではポリシーが使用されます。

<AuthorizeView Policy="BillingAdministrator">
    <Authorized>
        <p>
            The user is in the 'Billing Administrator' ME-ID Administrator Role
            and can see this content.
        </p>
    </Authorized>
    <NotAuthorized>
        <p>
            The user is NOT in the 'Billing Administrator' role and sees this
            content.
        </p>
    </NotAuthorized>
</AuthorizeView>

コンポーネント全体へのアクセスは、[Authorize] 属性ディレクティブ (AuthorizeAttribute) を使用するポリシーを基にして行うことができます。

@page "/"
@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "BillingAdministrator")]

ユーザーが承認されていない場合は、ME-ID サインイン ページにリダイレクトされます。

ポリシー チェックは、手続き型ロジックを使用してコードで実行することもできます。

CheckPolicy.razor:

@page "/checkpolicy"
@using Microsoft.AspNetCore.Authorization
@inject IAuthorizationService AuthorizationService

<h1>Check Policy</h1>

<p>This component checks a policy in code.</p>

<button @onclick="CheckPolicy">Check 'BillingAdministrator' policy</button>

<p>Policy Message: @policyMessage</p>

@code {
    private string policyMessage = "Check hasn't been made yet.";

    [CascadingParameter]
    private Task<AuthenticationState> authenticationStateTask { get; set; }

    private async Task CheckPolicy()
    {
        var user = (await authenticationStateTask).User;

        if ((await AuthorizationService.AuthorizeAsync(user, 
            "BillingAdministrator")).Succeeded)
        {
            policyMessage = "Yes! The 'BillingAdministrator' policy is met.";
        }
        else
        {
            policyMessage = "No! 'BillingAdministrator' policy is NOT met.";
        }
    }
}

上記の方法を使用して、セキュリティ グループのポリシー ベースのアクセスを作成することもできます。この場合、ポリシーに使用される GUID は、以下と一致します

アプリ ロール

Azure portal で、アプリをアプリ ロールメンバーシップ要求を提供するように構成するには、ME-ID ドキュメントの、「アプリケーションにアプリ ロールを追加し、それらをトークン で受け取る」を参照してください。

以下の例では、アプリが 2 つのロールを使用して構成されていること、およびロールがテスト ユーザーに割り当てられていることを前提としています。

  • Admin
  • Developer

ME-ID Premium アカウントがないとグループにロールを割り当てることはできませんが、Standard Azure アカウントを使用して、ユーザーにロールを割り当て、ユーザーに対するロール要求を受け取ることができます。 このセクションのガイダンスでは、ME-ID Premium アカウントは必要ありません。

次のいずれかの方法で、ME-ID にアプリ ロールを追加します。

  • 規定のディレクトリを使用する場合は、「アプリケーションにアプリ ロールを追加してトークンで受け取る」のガイダンスに従い、ME-ID ロールを作成します。

  • 既定のディレクトリを使用していない場合は、Azure portal でアプリのマニフェストを編集し、マニフェスト ファイルの appRoles エントリでアプリのロールを手動で確立します。 Admin ロールと Developer ロールを作成する appRoles エントリの例を次に示します。 これらのサンプル ロールは、後でコンポーネント レベルでアクセス制限を実装するために使用されます。

    重要

    次の方法は、Azure アカウントの既定のディレクトリに登録されていないアプリにのみ推奨されます。 既定のディレクトリに登録されているアプリについては、この一覧の前の箇条書きを参照してください。

    "appRoles": [
      {
        "allowedMemberTypes": [
          "User"
        ],
        "description": "Administrators manage developers.",
        "displayName": "Admin",
        "id": "584e483a-7101-404b-9bb1-83bf9463e335",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Admin"
      },
      {
        "allowedMemberTypes": [
          "User"
        ],
        "description": "Developers write code.",
        "displayName": "Developer",
        "id": "82770d35-2a93-4182-b3f5-3d7bfe9dfe46",
        "isEnabled": true,
        "lang": null,
        "origin": "Application",
        "value": "Developer"
      }
    ],
    

ユーザー (または Premium レベルの Azure アカウントがある場合はグループ) にロールを割り当てるには:

  1. Azure portal の ME-ID 領域で [エンタープライズ アプリケーション] に移動します。
  2. アプリを選びます。 サイド バーから [管理]>[ユーザーとグループ] を選択します。
  3. 1 つ以上のユーザー アカウントのチェックボックスをオンにします。
  4. ユーザーの一覧の上にあるメニューから、[割り当ての編集] を選択します。
  5. [ロールを選択する] エントリに対して、[選択されていません] を選択します。
  6. 一覧からロールを選択し、[選択] ボタンを使用して選択します。
  7. 画面の下部にある [割り当て] ボタンを使用して、ロールを割り当てます。

Azure portal で各追加ロール割り当てに対して ユーザーを再追加する ことにより、複数のロールを割り当てます。 ユーザーの一覧の上部にある [ユーザーまたはグループの追加] ボタンを使用して、ユーザーを再追加します。 前の手順を使用して、ユーザーに別のロールを割り当てます。 このプロセスを必要な回数だけ繰り返して、ユーザー (またはグループ) にロールを追加できます。

カスタム ユーザー アカウント」セクションで示されている CustomAccountFactory は、JSON 配列値を持つ role 要求に対して動作するように設定されています。 「カスタム ユーザー アカウント」セクションで示されているように、アプリに CustomAccountFactory を追加して登録します。 元の role 要求はフレームワークによって自動的に削除されるため、それを削除するためのコードを提供する必要はありません。

Program ファイルで、"role" という名前の要求を、ClaimsPrincipal.IsInRole チェックのロール要求として追加または確認します。

builder.Services.AddMsalAuthentication(options =>
{
    ...

    options.UserOptions.RoleClaim = "role";
});

Note

directoryRoles 要求 (ME-ID 管理者ロール) を使用したい場合は、"directoryRoles" を RemoteAuthenticationUserOptions.RoleClaim に割り当てます。

前の手順を完了してロールを作成し、ユーザー (または、Premium レベルの Azure アカウントを持っている場合はグループ) に割り当て、前述のこの記事と「ASP.NET Core Blazor WebAssembly で Graph API を使用する」で説明されているように Graph SDK で CustomAccountFactory を実装したら、サインインしているユーザーが割り当てられている各割り当て済みロール (またはユーザーが所属しているグループに割り当てられているロール) の role 要求が表示されます。 テスト ユーザーを使ってアプリを実行し、要求が期待どおりに存在することを確認します。 Graph SDK をローカル環境でテストする場合は、残っている Cookie がテストに干渉しないよう、テストのたびに新しいプライベートまたはシークレット ブラウザー セッションを使うことをお勧めします。 詳しくは、「Microsoft Entra ID を使用して、ASP.NET Core Blazor WebAssembly スタンドアロン アプリをセキュリティで保護する」をご覧ください。

この時点でコンポーネントの承認方法が機能しています。 アプリのコンポーネント内のすべての認可メカニズムで、Admin ロールを使用してユーザーを承認できます。

複数のロール テストがサポートされています。

  • AuthorizeView コンポーネントを使用して、ユーザーが Admin または Developer ロールのいずれかであることを要求します。

    <AuthorizeView Roles="Admin, Developer">
        ...
    </AuthorizeView>
    
  • AuthorizeView コンポーネントを使用して、ユーザーが Admin および Developer ロールの両方であることを要求します。

    <AuthorizeView Roles="Admin">
        <AuthorizeView Roles="Developer" Context="innerContext">
            ...
        </AuthorizeView>
    </AuthorizeView>
    

    内部 AuthorizeViewContext に関する詳細情報については、「ASP.NET Core Blazor 認証および許可」を参照してください。

  • [Authorize] 属性を使用して、ユーザーが Admin または Developer ロールのいずれかであることを要求します。

    @attribute [Authorize(Roles = "Admin, Developer")]
    
  • [Authorize] 属性を使用して、ユーザーが Admin および Developer ロールの両方であることを要求します。

    @attribute [Authorize(Roles = "Admin")]
    @attribute [Authorize(Roles = "Developer")]
    
  • 手続き型のコードを使用して、ユーザーが Admin または Developer ロールのいずれかであることを要求します。

    @code {
        private async Task DoSomething()
        {
            var authState = await AuthenticationStateProvider
                .GetAuthenticationStateAsync();
            var user = authState.User;
    
            if (user.IsInRole("Admin") || user.IsInRole("Developer"))
            {
                ...
            }
            else
            {
                ...
            }
        }
    }
    
  • 手続き型のコードを使用して、ユーザーが Admin および Developer ロールの両方であることを要求します。それには、前の例の条件付き OR (||)条件付き AND (&&) に変更します。

    if (user.IsInRole("Admin") && user.IsInRole("Developer"))
    

複数のロール テストがサポートされています。

  • [Authorize] 属性を使用して、ユーザーが Admin または Developer ロールのいずれかであることを要求します。

    [Authorize(Roles = "Admin, Developer")]
    
  • [Authorize] 属性を使用して、ユーザーが Admin および Developer ロールの両方であることを要求します。

    [Authorize(Roles = "Admin")]
    [Authorize(Roles = "Developer")]
    
  • 手続き型のコードを使用して、ユーザーが Admin または Developer ロールのいずれかであることを要求します。

    static readonly string[] scopeRequiredByApi = new string[] { "API.Access" };
    
    ...
    
    [HttpGet]
    public IEnumerable<ReturnType> Get()
    {
        HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi);
    
        if (User.IsInRole("Admin") || User.IsInRole("Developer"))
        {
            ...
        }
        else
        {
            ...
        }
    
        return ...
    }
    
  • 手続き型のコードを使用して、ユーザーが Admin および Developer ロールの両方であることを要求します。それには、前の例の条件付き OR (||)条件付き AND (&&) に変更します。

    if (User.IsInRole("Admin") && User.IsInRole("Developer"))
    

.NET 文字列の比較は既定では大文字と小文字が区別されるため、ロール名の照合でも大文字と小文字が区別されます。 たとえば、Admin (大文字 A) は、admin (小文字 a) と同じロールとして扱われません。

通常、パスカル ケースはロール名に使用されますが (例: BillingAdministrator)、パスカル ケースの使用は厳密な要件ではありません。 キャメル ケース、ケバブ ケース、スネーク ケースなど、さまざまなケース スキームが許可されています。 ロール名でのスペースの使用も通常とは異なりますが、許可されています。 たとえば、billing administrator は .NET アプリでは通常とは異なるロール名の形式ですが、有効です。

その他のリソース