Identity を使用して SPA の Web API バックエンドをセキュリティで保護する方法

Note

これは、この記事の最新バージョンではありません。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

警告

このバージョンの ASP.NET Core はサポート対象から除外されました。 詳細については、「.NET および .NET Core サポート ポリシー」を参照してください。 現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

重要

この情報はリリース前の製品に関する事項であり、正式版がリリースされるまでに大幅に変更される可能性があります。 Microsoft はここに示されている情報について、明示か黙示かを問わず、一切保証しません。

現在のリリースについては、この記事の .NET 8 バージョンを参照してください。

ASP.NET Core のIdentity には、認証、認可、identity 管理を処理する API が用意されています。 このAPI を使うと、cookie ベースの認証を使って、Web API バックエンドのエンドポイントをセキュリティで保護できます。 Cookie を使用できないクライアントではトークンベースのオプションを使用できますが、これを使用する場合は、トークンのセキュリティを確保する必要があります。 ブラウザー ベースのアプリケーションには Cookie を使用することをお勧めします。既定では、ブラウザが cookie を JavaScript に公開せずに自動的に処理するためです。

この記事では、Identity を使って、Angular、React、Vue アプリなどの SPA 用 Web API バックエンドをセキュリティで保護する方法を説明します。 同じバックエンド API を使って、Blazor WebAssembly アプリをセキュリティ保護できます。

前提条件

この記事で示す手順では、次のような ASP.NET Core Web API アプリに認証と認可を追加します。

  • 認証用にまだ構成されていません。
  • net8.0 以降をターゲットとします。
  • Minimal API またはコントローラー ベースの API を使用できます。

この記事の一部のテスト手順では、プロジェクト テンプレートに含まれている Swagger UI を使用します。 Swagger UI では、Web API バックエンドで Identity を使用する必要はありません。

NuGet パッケージのインストール

次の NuGet パッケージをインストールします。

最も簡単に作業を始めるには、メモリ内データベースを使います。

後で、テストまたは運用環境で使うときにセッション間でユーザー データを保存するには、SQLite または SQL Server にデータベースを変更します。 そのようにすると、EF Core の概要チュートリアルで示されているように、移行を通じてデータベースを作成する必要があるため、メモリ内と比較して若干複雑になります。

これらのパッケージをインストールするには、Visual Studio の NuGet パッケージ マネージャーまたは dotnet add package CLI コマンドを使います。

IdentityDbContext を作成します

IdentityDbContext<TUser> から継承する ApplicationDbContext という名前のクラスを追加します。

using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;

public class ApplicationDbContext : IdentityDbContext<IdentityUser>
{
    public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options) :
        base(options)
    { }
}

示されているコードでは、さまざまな環境用のデータベースを構成できる特別なコンストラクターが提供されます。

以下の手順で示すコードを追加するときは、必要に応じて次の using ディレクティブを 1 つ以上追加します。

using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore;

EF Core コンテキストを構成する

前に説明したように、最も簡単に作業を始められる方法は、メモリ内データベースを使うことです。 メモリ内の場合は、各実行は新しいデータベースを使って開始され、移行を使う必要はありません。 WebApplication.CreateBuilder(args) の呼び出しの後に、メモリ内データベースを使うように Identity を構成する次のコードを追加します。

builder.Services.AddDbContext<ApplicationDbContext>(
    options => options.UseInMemoryDatabase("AppDb"));

テストまたは運用環境で使うときにセッション間でユーザー データを保存するには、後で SQLite または SQL Server にデータベースを変更します。

Identity サービスをコンテナーに追加する

WebApplication.CreateBuilder(args) を呼び出した後、AddAuthorization を呼び出して、依存関係挿入 (DI) コンテナーにサービスを追加します。

builder.Services.AddAuthorization();

Identity API をアクティブにする

WebApplication.CreateBuilder(args) を呼び出した後で、AddIdentityApiEndpoints<TUser>(IServiceCollection)AddEntityFrameworkStores<TContext>(IdentityBuilder) を呼び出します。

builder.Services.AddIdentityApiEndpoints<IdentityUser>()
    .AddEntityFrameworkStores<ApplicationDbContext>();

既定では、cookie と固有のトークンの両方がアクティブになります。 ログイン エンドポイントの useCookies クエリ文字列パラメーターが true の場合、ログイン時に Cookie およびトークンが発行されます。

Identity ルートをマップする

builder.Build() の呼び出しの後で、MapIdentityApi<TUser>(IEndpointRouteBuilder) を呼び出して Identity エンドポイントをマップします。

app.MapIdentityApi<IdentityUser>();

選択したエンドポイントをセキュリティで保護する

エンドポイントをセキュリティで保護するには、ルートを定義する Map{Method} の呼び出しで RequireAuthorization 拡張メソッドを使います。 次に例を示します。

app.MapGet("/weatherforecast", (HttpContext httpContext) =>
{
    var forecast = Enumerable.Range(1, 5).Select(index =>
        new WeatherForecast
        {
            Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)),
            TemperatureC = Random.Shared.Next(-20, 55),
            Summary = summaries[Random.Shared.Next(summaries.Length)]
        })
        .ToArray();
    return forecast;
})
.WithName("GetWeatherForecast")
.WithOpenApi()
.RequireAuthorization();

RequireAuthorization メソッドは、次の場合にも使用できます。

  • 次の例に示すように、Swagger UI エンドポイントをセキュリティ保護します。

    app.MapSwagger().RequireAuthorization();
    
  • 次の例に示すように、特定の要求またはアクセス許可を使用してセキュリティ保護します。

    .RequireAuthorization("Admin");
    

コントローラー ベースの Web API プロジェクトでは、[Authorize] 属性をコントローラーまたはアクションに適用することによって、エンドポイントをセキュリティで保護します。

API のテスト

認証をテストする簡単な方法は、メモリ内データベースとプロジェクト テンプレートに含まれる Swagger UI を使うことです。 次の手順では、Swagger UI を使って API をテストする方法を示します。 Swagger UI エンドポイントがセキュリティ保護されていないことを確認します。

セキュリティ保護されたエンドポイントへのアクセスを試みる

  • アプリを実行して、Swagger UI に移動します。
  • Web API テンプレートによって作成されたプロジェクトの /weatherforecast など、セキュリティ保護されたエンドポイントを展開します。
  • [試してみる] を選択します。
  • [実行] を選択します。 応答は 401 - not authorized です。

テスト登録

  • /register を展開して、[試してみる] を選びます。

  • UI の [パラメーター] セクションに、サンプルの要求本文が表示されます。

    {
      "email": "string",
      "password": "string"
    }
    
  • "string" を有効なメール アドレスとパスワードに置き換えて、[実行] を選びます。

    既定のパスワード検証規則に準拠するには、パスワードの長さは 6 文字以上で、次の文字の少なくとも 1 つを含む必要があります。

    • 大文字
    • 小文字
    • 数字
    • 英数字以外の文字

    無効なメール アドレスまたは無効なパスワードを入力すると、結果に検証エラーが含まれます。 検証エラーを含む応答本文の例を次に示します。

    {
      "type": "https://tools.ietf.org/html/rfc9110#section-15.5.1",
      "title": "One or more validation errors occurred.",
      "status": 400,
      "errors": {
        "PasswordTooShort": [
          "Passwords must be at least 6 characters."
        ],
        "PasswordRequiresNonAlphanumeric": [
          "Passwords must have at least one non alphanumeric character."
        ],
        "PasswordRequiresDigit": [
          "Passwords must have at least one digit ('0'-'9')."
        ],
        "PasswordRequiresLower": [
          "Passwords must have at least one lowercase ('a'-'z')."
        ]
      }
    }
    

    エラーは ProblemDetails 形式で返されるため、クライアントはそれを解析し、必要に応じて検証エラーを表示できます。

    登録が成功すると、結果は 200 - OK 応答になります。

テスト ログイン

  • /login を展開して、[試してみる] を選びます。要求本文の例では、2 つの追加パラメーターが示されています。

    {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
      "twoFactorRecoveryCode": "string"
    }
    

    この例では、追加の JSON プロパティは必要ないので、削除してかまいません。 useCookiestrue に設定します。

  • "string" を登録に使ったメール アドレスとパスワードに置き換えてから、[実行] を選びます。

    ログインが成功すると、結果は応答ヘッダーに cookie が含まれる 200 - OK 応答になります。

セキュリティ保護されたエンドポイントを再テストする

ログインが成功したら、セキュリティ保護されたエンドポイントをもう一度実行します。 要求と共に認証 cookie が自動的に送信されて、エンドポイントが認可されます。 Cookie ベースの認証は、ブラウザーに安全に組み込まれており、"そのままで機能します"。

ブラウザー以外のクライアントでのテスト

一部の Web クライアントでは、既定でヘッダーに Cookie が含まれない場合があります。

  • API のテストにツールを使っている場合は、設定で Cookie を有効にすることが必要な場合があります。

  • JavaScript の fetch API には、既定では Cookie が含まれません。 オプションで credentials を値 include に設定することで、それを有効にします。

  • Blazor WebAssembly アプリで実行されている HttpClient では、資格情報を含めるために HttpRequestMessage が必要です。

    request.SetBrowserRequestCredential(BrowserRequestCredentials.Include);
    

トークン ベースの認証を使用する

ブラウザー ベースのアプリケーションには Cookie を使用することをお勧めします。既定では、ブラウザが cookie を JavaScript に公開せずに自動的に処理するためです。

後続の要求を認証するために使用できるカスタム トークン (ASP.NET Core identity プラットフォームに固有のもの) が発行されます。 このトークンは、ベアラー トークンとして Authorization ヘッダーに渡されます。 更新トークンも提供されます。 このトークンを使うと、古いトークンの有効期限が切れたときに、ユーザーに再度ログインを強制することなく、アプリケーションで新しいトークンを要求できます。

このトークンは標準の JSON Web Token (JWT) ではありません。 組み込み Identity API は主に単純なシナリオ向けであるため、カスタム トークンの使用は意図的です。 トークン オプションは、フル機能の identity サービス プロバイダーまたはトークン サーバーになることを目的としたものではなく、Cookie を使用できないクライアント向けの cookie オプションの代替として使用されます。

トークンベースの認証を使用するには、/login エンドポイントを呼び出すときに、useCookies クエリ文字列パラメーターを false に設定します。 トークンはベアラー認証スキームを使用します。 /login への呼び出しから返されたトークンを使用して、保護されたエンドポイントへの後続の呼び出しで、<token> がアクセス トークンであるヘッダー Authorization: Bearer <token> を追加する必要があります。 詳細については、この記事で後述する「POST /login エンドポイントの使用」を参照してください。

ログアウト

ユーザーがログアウトする方法を提供するには、次の例のような /logout エンドポイントを定義します。

app.MapPost("/logout", async (SignInManager<IdentityUser> signInManager,
    [FromBody] object empty) =>
{
    if (empty != null)
    {
        await signInManager.SignOutAsync();
        return Results.Ok();
    }
    return Results.Unauthorized();
})
.WithOpenApi()
.RequireAuthorization();

このエンドポイントを呼び出すときは、要求本文に空の JSON オブジェクト ({}) を指定します。 次のコードは、ログアウト エンドポイントの呼び出しの例です。

public signOut() {
  return this.http.post('/logout', {}, {
    withCredentials: true,
    observe: 'response',
    responseType: 'text'

MapIdentityApi<TUser> エンドポイント

MapIdentityApi<TUser> を呼び出すと、次のエンドポイントがアプリに追加されます。

POST /register エンドポイントを使用する

要求本文には、次の Email プロパティと Password プロパティが必要です。

{
  "email": "string",
  "password": "string",
}

詳細については、以下を参照してください:

POST /login エンドポイントを使用する

要求本文で、EmailPassword が必須です。 2 要素認証 (2FA) が有効になっている場合、または TwoFactorCode TwoFactorRecoveryCode が必須です。 2FA が有効になっていない場合は、twoFactorCodetwoFactorRecoveryCode の両方を省略します。 詳細については、この記事で後述する「POST /manage/2fa エンドポイントの使用」を参照してください。

2FA が有効になっていない要求本文の例を次に示します。

{
  "email": "string",
  "password": "string"
}

2FA が有効になっている要求本文の例を次に示します。

  • {
      "email": "string",
      "password": "string",
      "twoFactorCode": "string",
    }
    
  • {
      "email": "string",
      "password": "string",
      "twoFactorRecoveryCode": "string"
    }
    

エンドポイントには、クエリ文字列パラメーターが必要です。

  • useCookies - cookie ベース認証に true を設定します。 トークン ベースの認証に対して false に設定または省略します。

cookie ベース認証の詳細については、この記事の「ログインのテスト」を参照してください。

トークンベースの認証

useCookiesfalse である、または省略された場合、トークンベースの認証が有効になります。 応答本文には、次のプロパティが含まれています。

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

これらのプロパティの詳細については、「AccessTokenResponse」を参照してください。

次の例に示すように、認証された要求を行うヘッダーにアクセス トークンを配置します。

Authorization: Bearer {access token}

アクセス トークンの有効期限が迫ったら、/refresh エンドポイントを呼び出します。

POST /refresh エンドポイントを使用する

トークン ベースの認証でのみ使用します。 ユーザーに再度ログインを強制することなく、新しいアクセス トークンを取得します。 アクセス トークンの有効期限が切れるときに、このエンドポイントを呼び出します。

要求本文には、RefreshToken のみが含まれています。 要求本文の例を次に示します。

{
  "refreshToken": "string"
}

呼び出しが成功した場合、次の例に示すように、応答本文は新しい AccessTokenResponse になります。

{
  "tokenType": "string",
  "accessToken": "string",
  "expiresIn": 0,
  "refreshToken": "string"
}

GET /confirmEmail エンドポイントを使用する

Identity が電子メールの確認のために設定されている場合、/register エンドポイントへの後続の呼び出しが成功すると、/confirmEmail エンドポイントへのリンクを含む電子メールが送信されます。 このリンクには、次のクエリ文字列パラメーターが含まれています。

  • userId
  • code
  • changedEmail - 登録時にユーザーがメール アドレスを変更した場合にのみ含まれます。

Identity は、確認メールの既定のテキストを提供します。 既定では、メールの件名は "メールの確認" であり、メール本文は次の例のようになります。

 Please confirm your account by <a href='https://contoso.com/confirmEmail?userId={user ID}&code={generated code}&changedEmail={new email address}'>clicking here</a>.

RequireConfirmedEmail プロパティが true に設定されている場合、ユーザーは電子メール内のリンクをクリックして電子メール アドレスが確認されるまでログインできません。 /confirmEmail エンドポイント:

  • メール アドレスを確認し、ユーザーがログインできるようにします。
  • 応答本文に "メールを確認していただきありがとうございます" というテキストを返します。

電子メール確認用に Identity を設定するには、Program.csRequireConfirmedEmailtrue に設定するコードを追加し、DI コンテナーに IEmailSender を実装するクラスを追加します。 次に例を示します。

builder.Services.Configure<IdentityOptions>(options =>
{
    options.SignIn.RequireConfirmedEmail = true;
});

builder.Services.AddTransient<IEmailSender, EmailSender>();

詳細については、「ASP.NET Core でのアカウントの確認とパスワードの回復」をご覧ください。

Identity は、2FA やパスワードのリセットなど、送信する必要がある他のメールにも既定のテキストを提供します。 これらのメールをカスタマイズするには、IEmailSender インターフェイスのカスタム実装を提供します。 前の例では、EmailSenderIEmailSender を実装するクラスです。 IEmailSender を実装するクラスの例を含む詳細については、「ASP.NET Core でのアカウントの確認とパスワードの回復」参照してください。

POST /resendConfirmationEmail エンドポイントを使用する

アドレスが登録済みユーザーに対して有効な場合にのみ、電子メールを送信します。

要求本文には、Email のみが含まれています。 要求本文の例を次に示します。

{
  "email": "string"
}

詳細については、この記事で前述した「GET /confirmEmail エンドポイントの使用」を参照してください。

POST /forgotPassword エンドポイントを使用する

パスワード リセット コードを含む電子メールを生成します。 そのコードを新しいパスワードで /resetPassword に送信します。

要求本文には、Email のみが含まれています。 次に例を示します。

{
  "email": "string"
}

Identity が電子メールを送信できるようにする方法については、「GET /confirmEmail エンドポイントを使用する」を参照してください。

POST /resetPassword エンドポイントを使用する

/forgotPassword エンドポイントを呼び出してリセット コードを取得した後、このエンドポイントを呼び出します。

要求本文には、EmailResetCode、および NewPasswordが必要です。 次に例を示します。

{
  "email": "string",
  "resetCode": "string",
  "newPassword": "string"
}

POST /manage/2fa エンドポイントを使用する

ユーザーの 2 要素認証 (2FA) を構成します。 2FA が有効になっている場合、ログインが成功するには、電子メール アドレスとパスワードに加えて、認証アプリによって生成されたコードが必要です。

2FA を有効にする

現在認証されているユーザーに対して 2FA を有効にするには:

  • /manage/2fa エンドポイントを呼び出し、要求本文で空の JSON オブジェクト ({}) を送信します。

  • 応答本文には、SharedKey と、この時点では必要ないその他のプロパティが用意されています。 共有キーは、認証アプリを設定するために使用されます。 応答本文の例:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 0,
      "recoveryCodes": null,
      "isTwoFactorEnabled": false,
      "isMachineRemembered": false
    }
    
  • 共有キーを使用して、時間ベースのワンタイム パスワード (TOTP) を取得します。 詳細については、「ASP.NET Core での TOTP authenticator アプリの QR コード生成を有効にする」を参照してください。

  • /manage/2fa エンドポイントを呼び出し、要求本文で TOTP と "enable": true を送信します。 次に例を示します。

    {
      "enable": true,
      "twoFactorCode": "string"
    }
    
  • 応答本文は、IsTwoFactorEnabled が true であることを確認し、RecoveryCodes を提供します。 回復コードは、認証アプリが使用できない場合にログインするために使用されます。 2FA を正常に有効にした後の応答本文の例:

    {
      "sharedKey": "string",
      "recoveryCodesLeft": 10,
      "recoveryCodes": [
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string",
        "string"
      ],
      "isTwoFactorEnabled": true,
      "isMachineRemembered": false
    }
    

2FA でログインする

/login エンドポイントを呼び出し、要求本文で電子メール アドレス、パスワード、および TOTP を送信します。 次に例を示します。

{
  "email": "string",
  "password": "string",
  "twoFactorCode": "string"
}

ユーザーが認証アプリにアクセスできない場合は、2FA が有効になったときに指定された回復コードのいずれかを使用して /login エンドポイントを呼び出してログインします。 要求の本文は、次の例のようになります。

{
  "email": "string",
  "password": "string",
  "twoFactorRecoveryCode": "string"
}

回復コードをリセットする

回復コードの新しいコレクションを取得するには、ResetRecoveryCodestrue に設定してこのエンドポイントを呼び出します。 要求本文の例を次に示します。

{
  "resetRecoveryCodes": true
}

共有キーをリセットする

新しいランダム共有キーを取得するには、ResetSharedKeytrue に設定してこのエンドポイントを呼び出します。 要求本文の例を次に示します。

{
  "resetSharedKey": true
}

キーをリセットすると、後の要求によって再び有効になるまで、認証されたユーザーの 2 要素ログイン要件が自動的に無効になります。

コンピューターを忘れる

cookie "remember me flag" (存在する場合) をクリアするには、ForgetMachine を true に設定してこのエンドポイントを呼び出します。 要求本文の例を次に示します。

{
  "forgetMachine": true
}

このエンドポイントは、トークンベースの認証には影響しません。

GET /manage/info エンドポイントを使用する

ログインしているユーザーのメール アドレスと電子メール確認の状態を取得します。 セキュリティ上の理由から、このエンドポイントから要求が省略されました。 要求が必要な場合は、サーバー側 API を使用して要求のエンドポイントを設定します。 または、すべてのユーザーの要求を共有する代わりに、要求を受け入れ、検証エンドポイントを指定します。検証エンドポイントは、要求を受け付けてユーザーが要求を持っているかどうかを応答します。

要求にはパラメーターは必要ありません。 応答本文には、次の例のように、Email プロパティと IsEmailConfirmed プロパティが含まれています。

{
  "email": "string",
  "isEmailConfirmed": true
}

POST /manage/info エンドポイントを使用する

ログインしているユーザーのメール アドレスとパスワードを更新します。 次の例に示すように、要求本文で NewEmailNewPassword、および OldPassword を送信します。

{
  "newEmail": "string",
  "newPassword": "string",
  "oldPassword": "string"
}

応答の本文の例を次に示します。

{
  "email": "string",
  "isEmailConfirmed": false
}

関連項目

詳細については、次のリソースを参照してください。

ASP.NET Core テンプレートは、API 承認のサポートを使用して、シングル ページ アプリ (SPA) で認証を提供します。 ユーザーを認証および格納するための ASP.NET Core Identity が Duende Identity Server と組み合わされ、OpenID Connect が実装されます。

重要

Duende Software により、Duende Identity Server を実稼働で使用することのライセンス料の支払いが求められる場合があります。 詳細については、「ASP.NET Core 5.0 から 6.0 への移行」を参照してください。

認証パラメーターが Angular および React プロジェクト テンプレートに追加されました。これは、Web アプリケーション (Model-View-Controller) (MVC) および Web アプリケーション(Razor Pages) プロジェクト テンプレートの認証パラメーターに似ています。 許可されているパラメーター値は、NoneIndividual です。 現時点では、React.js および Redux プロジェクト テンプレートは認証パラメータをサポートしていません。

API 承認のサポートを使用してアプリを作成する

ユーザーの認証と承認は、Angular と React SPA の両方で使用できます。 コマンド シェルを開き、次のコマンドを実行します。

Angular:

dotnet new angular -au Individual

React:

dotnet new react -au Individual

前のコマンドによって、SPA を含む ClientApp ディレクトリで ASP.NET Core アプリが作成されます。

アプリの ASP.NET Core コンポーネントの一般的な説明

次のセクションでは、認証のサポートが含まれる場合のプロジェクトへの追加について説明します。

Program.cs

次のコード例は、Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet パッケージに依存しています。 この例では、AddApiAuthorization および AddIdentityServerJwt 拡張メソッドを使用して、API の認証と承認を構成します。 認証ありの React または Angular SPA プロジェクト テンプレートを使用するプロジェクトには、このパッケージへの参照が含まれます。

dotnet new angular -au Individual では、次の Program.cs ファイルが生成されます。

using Microsoft.AspNetCore.Authentication;
using Microsoft.EntityFrameworkCore;
using output_directory_name.Data;
using output_directory_name.Models;

var builder = WebApplication.CreateBuilder(args);

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
    options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddIdentityServer()
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.AddControllersWithViews();
builder.Services.AddRazorPages();

var app = builder.Build();

if (app.Environment.IsDevelopment())
{
    app.UseMigrationsEndPoint();
}
else
{
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();

app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller}/{action=Index}/{id?}");
app.MapRazorPages();

app.MapFallbackToFile("index.html");

app.Run();

上記のコードにより、以下が構成されます。

  • 既定の UI を使用する Identity:

    builder.Services.AddDbContext<ApplicationDbContext>(options =>
        options.UseSqlite(connectionString));
    builder.Services.AddDatabaseDeveloperPageExceptionFilter();
    
    builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
        .AddEntityFrameworkStores<ApplicationDbContext>();
    
  • IdentityServer には、IdentityServer の上にいくつかの既定の ASP.NET Core 規則を設定する追加の AddApiAuthorization ヘルパー メソッドがあります。

    builder.Services.AddIdentityServer()
        .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
    
  • IdentityServer によって生成された JWT トークンを検証するようにアプリを構成する AddIdentityServerJwt ヘルパー メソッドが追加された Authentication。

    builder.Services.AddAuthentication()
    .AddIdentityServerJwt();
    
  • 要求の資格情報の検証と、要求コンテキストでのユーザーの設定を行う認証ミドルウェア:

    app.UseAuthentication();
    
  • OpenID Connect エンドポイントを公開する IdentityServer ミドルウェア:

    app.UseIdentityServer();
    

警告

この記事では、接続文字列の使用方法について説明します。 ローカル データベースでは、ユーザーを認証する必要はありませんが、運用環境では、接続文字列認証用のパスワードが含まれる場合があります。 リソース所有者パスワード資格情報 (ROPC) は、運用データベースで回避する必要があるセキュリティ リスクです。 運用アプリでは、使用可能な最も安全な認証フローを使用する必要があります。 テスト環境または運用環境にデプロイされたアプリの認証の詳細については、「 安全な認証フロー」を参照してください。

Azure App Service on Linux

Linux での Azure App Service デプロイについては、発行者を明示的に指定します。

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

前のコードでは、{AUTHORITY} プレースホルダーが、OpenID Connect 呼び出し時に使用する Authority です。

例:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

このヘルパー メソッドは、サポートされている構成を使用するように IdentityServer を構成します。 IdentityServer は、アプリのセキュリティの問題を処理するための強力で拡張可能なフレームワークです。 同時に、ほとんどの一般的なシナリオには必要のない複雑さが発生します。 そのため、使用開始時に適切であると考えられる一連の規則と構成オプションが用意されています。 認証のニーズが変わった場合でも、IdentityServer の全機能を活用して、ニーズに合わせて認証をカスタマイズできます。

AddIdentityServerJwt

ヘルパー メソッドでは、アプリに対するポリシー スキームが既定の認証ハンドラーとして構成されます。 そのポリシーは、Identity の URL 空間 "/Identity" のサブパスにルーティングされたすべての要求を Identity で処理できるように構成されています。 それ以外のすべての要求は、JwtBearerHandler で処理されます。 さらに、このメソッドは、<<ApplicationName>>API API リソースを既定のスコープ <<ApplicationName>>API で IdentityServer に登録し、アプリ用に IdentityServer によって発行されたトークンを検証するように JWT ベアラー トークン ミドルウェアを構成します。

WeatherForecastController

このファイルでは、[Authorize] 属性がクラスに適用されていることに注意してください。これは、リソースにアクセスするための既定のポリシーに基づいてユーザーを承認する必要があることを示します。 既定の承認ポリシーは、前に説明したポリシー スキームに対して AddIdentityServerJwt によって設定される既定の認証スキームを使用するように構成され、このようなヘルパー メソッドによって構成された JwtBearerHandler は、アプリに対する要求の既定のハンドラーになります。

ApplicationDbContext

このファイルでは、同じ DbContext が Identity で使用されていることに注意してください。ただし、それが ApiAuthorizationDbContext (IdentityDbContext から派生したクラス) を拡張して、IdentityServer のスキーマを含める場合を除きます。

データベース スキーマを完全に制御するには、使用可能な IdentityDbContext クラスのいずれかを継承し、OnModelCreatingbuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) を呼び出すことで、Identity を含むようにコンテキストを構成します。

OidcConfigurationController

このファイルでは、クライアントが使用する必要のある OIDC パラメーターを提供するためにプロビジョニングされるエンドポイントに注意してください。

appsettings.json

プロジェクト ルートにある appsettings.json ファイルに、構成されているクライアントの一覧が記述されている新しい IdentityServer セクションがあります。 次の例には、1 つのクライアントがあります。 クライアント名はアプリケーション名に対応し、規則によって OAuth の ClientId パラメーターにマップされます。 構成対象のアプリの種類は、プロファイルによって示されています。 サーバーの構成プロセスを簡素化する規則を促進するために、内部的に使用されます。 「アプリケーション プロファイル」セクションで説明されているように、使用可能なプロファイルがいくつかあります。

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

プロジェクト ルートの appsettings.Development.json ファイルには、トークンの署名に使用されるキーについて説明している IdentityServer セクションがあります。 運用環境にデプロイする場合は、「運用環境へのデプロイ」セクションで説明されているように、アプリと共にキーをプロビジョニングしてデプロイする必要があります。

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Angular アプリの一般的な説明

Angular テンプレートでの認証と API 承認のサポートは、独自の Angular モジュールの ClientApp/src/api-authorization ディレクトリにあります。 モジュールは、次の要素で構成されています。

  • 3 つのコンポーネント:
    • login.component.ts: アプリのログイン フローを処理します。
    • logout.component.ts: アプリのログアウト フローを処理します。
    • login-menu.component.ts: 次のリンクのセットのいずれかを表示するウィジェット:
      • ユーザーが認証された場合のユーザー プロファイル管理とログアウトのリンク。
      • ユーザーが認証されていない場合の登録とログインのリンク。
  • ルートに追加でき、ルートにアクセスする前にユーザーを認証する必要があるルート セーフガード AuthorizeGuard
  • ユーザーが認証されるときに、API を対象とする発信 HTTP 要求にアクセス トークンを関連付ける HTTP インターセプター AuthorizeInterceptor
  • 認証プロセスの下位レベルの詳細を処理し、認証されたユーザーに関する情報をアプリの rest に公開して使用するサービス AuthorizeService
  • アプリの認証部分に関連付けられているルートを定義する Angular モジュール。 ログイン メニュー コンポーネント、インターセプター、セーフガード、サービスをアプリの rest から使用するために公開します。

React アプリの一般的な説明

React テンプレートでの認証と API 承認のサポートは、ClientApp/src/components/api-authorization ディレクトリにあります。 次の要素で構成されています。

  • 4 つのコンポーネント:
    • Login.js: アプリのログイン フローを処理します。
    • Logout.js: アプリのログアウト フローを処理します。
    • LoginMenu.js: 次のリンクのセットのいずれかを表示するウィジェット:
      • ユーザーが認証された場合のユーザー プロファイル管理とログアウトのリンク。
      • ユーザーが認証されていない場合の登録とログインのリンク。
    • AuthorizeRoute.js: Component パラメーターに示されているコンポーネントを表示する前に、ユーザーの認証を必要とするルート コンポーネント。
  • 認証プロセスの下位レベルの詳細を処理し、認証されたユーザーに関する情報をアプリの rest に公開して使用する、クラス AuthorizeService のエクスポートされた authService インスタンス。

これで、ソリューションの主要なコンポーネントを確認できました。次は、アプリの個々のシナリオについて詳しく見ていきましょう。

新しい API での承認が必要

既定では、システムは新しい API の承認を要求するように構成されています。 これを行うには、新しいコントローラーを作成し、コントローラー クラスまたはコントローラー内の任意のアクションに [Authorize] 属性を追加します。

API 認証ハンドラーをカスタマイズする

API の JWT ハンドラーの構成をカスタマイズするには、その JwtBearerOptions インスタンスを構成します。

builder.Services.AddAuthentication()
    .AddIdentityServerJwt();

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

API の JWT ハンドラーは、JwtBearerEvents を使用して認証プロセスを制御できるようにするイベントを発生させます。 API 承認のサポートを提供するために、AddIdentityServerJwt は独自のイベント ハンドラーを登録します。

イベントの処理をカスタマイズするには、必要に応じて追加のロジックを使用して既存のイベント ハンドラーをラップします。 次に例を示します。

builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

前のコードでは、OnTokenValidated イベント ハンドラーはカスタム実装に置き換えられています。 この実装では、次のことを実行します。

  1. API 承認のサポートによって提供される元の実装を呼び出します。
  2. 独自のカスタム ロジックを実行します。

クライアント側のルートを保護する (Angular)

クライアント側ルートの保護は、ルートを構成するときに実行するセーフガードのリストに承認セーフガードを追加することによって行われます。 例として、メイン アプリ Angular モジュール内で fetch-data ルートがどのように構成されているかを確認できます。

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

ルートを保護しても実際のエンドポイントは保護されず (まだ [Authorize] 属性が適用されている必要があります)、ユーザーが認証されていないときに、特定のクライアント側ルートに移動できないようにするのみであることに注意する必要があります。

API 要求の認証 (Angular)

アプリと共にホストされる API に対する要求の認証は、アプリによって定義された HTTP クライアント インターセプターを使用することによって自動的に行われます。

クライアント側のルートを保護する (React)

プレーン Route コンポーネントの代わりに AuthorizeRoute コンポーネントを使用して、クライアント側のルートを保護します。 たとえば、App コンポーネント内で fetch-data ルートがどのように構成されているかに注目してください。

<AuthorizeRoute path='/fetch-data' component={FetchData} />

ルートの保護:

  • 実際のエンドポイントを保護しません (まだ [Authorize] 属性が適用されている必要があります)。
  • ユーザーが認証されていないときに、特定のクライアント側ルートに移動できないようにします。

API 要求の認証 (React)

React を使用した要求の認証は、まず AuthorizeService から authService インスタンスをインポートすることによって行われます。 アクセス トークンは authService から取得され、次に示すように要求にアタッチされます。 React コンポーネントでは、通常、この作業は componentDidMount ライフサイクル メソッドで実行されるか、ユーザー操作の結果として行われます。

authService をコンポーネントにインポートする

import authService from './api-authorization/AuthorizeService'

アクセス トークンを取得して応答にアタッチする

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

運用環境への配置

アプリを運用環境にデプロイするには、次のリソースをプロビジョニングする必要があります。

  • Identity ユーザー アカウントと IdentityServer の権限を保存するデータベース。
  • トークンの署名に使用する運用証明書。
    • この証明書には特定の要件はありません。自己署名証明書、または CA 証明機関を通じてプロビジョニングされた証明書を使用できます。
    • PowerShell や OpenSSL などの標準ツールを使用して生成できます。
    • ターゲット コンピューターの証明書ストアにインストールすることも、強力なパスワードを使用して .pfx ファイルとして展開することもできます。

例: Azure 以外の Web ホスティング プロバイダーへの展開

Web ホスティング パネルで、ご自分の証明書を作成または読み込みます。 次に、アプリの appsettings.json ファイルで、IdentityServer セクションを変更してキーの詳細を含めます。 次に例を示します。

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

前の例の場合:

  • StoreName は、証明書が格納されている証明書ストアの名前を表します。 この例では、Web ホスティング ストアを指しています。
  • StoreLocation は、証明書の読み込み元を表します (この場合、CurrentUser)。
  • Name は、証明書の識別サブジェクトに対応します。

例: Azure App Service にデプロイする

このセクションでは、証明書ストアに格納されている証明書を使用した、Azure App Service へのアプリのデプロイについて説明します。 証明書ストアから証明書を読み込むようにアプリを変更するには、後の手順で、Azure portal でアプリを構成するときに、Standard レベル以上のサービス プランが必要です。

アプリの appsettings.json ファイルで、IdentityServer セクションを変更してキーの詳細を含めます。

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • ストア名は、証明書が格納されている証明書ストアの名前を表します。 この場合、個人ユーザー ストアを指しています。
  • ストアの場所は、証明書の読み込み元を表します (この場合、CurrentUser または LocalMachine)。
  • 証明書の name プロパティは、証明書の識別サブジェクトに対応します。

Azure App Service にデプロイするには、「Azure にアプリを配置する」の手順に従います。ここでは、必要な Azure リソースを作成し、アプリを運用環境にデプロイする方法を説明します。

前述の手順に従った後、アプリは Azure にデプロイされますが、まだ機能していません。 アプリによって使用される証明書は、Azure portal で構成する必要があります。 証明書の拇印を見つけて、証明書の読み込みに関するページで説明されている手順に従います。

これらの手順では SSL について説明していますが、Azure portal には、アプリで使用するプロビジョニング済みの証明書をアップロードできるプライベート証明書に関するセクションがあります。

Azure portal でアプリとアプリの設定を構成した後、ポータルでアプリを再起動します。

その他の構成オプション

API 承認のサポートは、SPA のエクスペリエンスを簡素化するために一連の規則、既定値、および拡張機能を備えた IdentityServer 上に構築されます。 言うまでもなく、ASP.NET Core 統合がシナリオをカバーしない場合は、IdentityServer の全機能をバックグラウンドで利用できます。 ASP.NET Core サポートでは、すべてのアプリが組織によって作成およびデプロイされる "ファーストパーティ" アプリに重点を置いています。 そのため、同意やフェデレーションなどのサポートは提供されていません。 このようなシナリオでは、IdentityServerを使用し、そのドキュメントに従ってください。

アプリケーション プロファイル

アプリケーション プロファイルは、そのパラメーターをさらに定義するアプリの事前定義された構成です。 現時点では、次のプロファイルがサポートされています。

  • IdentityServerSPA: IdentityServer と共にホストされる SPA を 1 つの単位として表します。
    • redirect_uri の既定値は /authentication/login-callback です。
    • post_logout_redirect_uri の既定値は /authentication/logout-callback です。
    • スコープのセットには、openidprofile、アプリ内の API に対して定義されているすべてのスコープが含まれます。
    • 許可される OIDC 応答の種類のセットには、id_token token があります。またはそれぞれ個別に指定します (id_tokentoken)。
    • 許可される応答モードは fragment です。
  • SPA: IdentityServer でホストされていない SPA を表します。
    • スコープのセットには、openidprofile、アプリ内の API に対して定義されているすべてのスコープが含まれます。
    • 許可される OIDC 応答の種類のセットには、id_token token があります。またはそれぞれ個別に指定します (id_tokentoken)。
    • 許可される応答モードは fragment です。
  • IdentityServerJwt: IdentityServer と一緒にホストされている API を表します。
    • アプリは、アプリ名を既定とする 1 つのスコープを持つように構成されています。
  • API: IdentityServer でホストされていない API を表します。
    • アプリは、アプリ名を既定とする 1 つのスコープを持つように構成されています。

AppSettings を使用した構成

構成システムを使用して、Clients または Resources の一覧にアプリを追加してそれらを構成します。

次の例で示すように、各クライアントの redirect_uri および post_logout_redirect_uri プロパティを構成します。

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

リソースを構成するときは、次に示すようにリソースのスコープを構成できます。

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

コードを使用した構成

また、オプションを構成するためのアクションを実行する AddApiAuthorization のオーバーロードを使用して、コードによってクライアントとリソースを構成することもできます。

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

その他の技術情報

ASP.NET Core 3.1 以降のテンプレートでは、API 承認のサポートを使用して、シングル ページ アプリ (SPA) で認証を提供します。 ユーザーの認証と保存を行う ASP.NET Core Identity は、OpenID Connect を実装するための IdentityServer と組み合わされています。

認証パラメーターが Angular および React プロジェクト テンプレートに追加されました。これは、Web アプリケーション (Model-View-Controller) (MVC) および Web アプリケーション(Razor Pages) プロジェクト テンプレートの認証パラメーターに似ています。 許可されているパラメーター値は、NoneIndividual です。 現時点では、React.js および Redux プロジェクト テンプレートは認証パラメータをサポートしていません。

API 承認のサポートを使用してアプリを作成する

ユーザーの認証と承認は、Angular と React SPA の両方で使用できます。 コマンド シェルを開き、次のコマンドを実行します。

Angular:

dotnet new angular -o <output_directory_name> 

React:

dotnet new react -o <output_directory_name> -au Individual

前のコマンドによって、SPA を含む ClientApp ディレクトリで ASP.NET Core アプリが作成されます。

アプリの ASP.NET Core コンポーネントの一般的な説明

次のセクションでは、認証のサポートが含まれる場合のプロジェクトへの追加について説明します。

Startup クラス

次のコード例は、Microsoft.AspNetCore.ApiAuthorization.IdentityServer NuGet パッケージに依存しています。 この例では、AddApiAuthorization および AddIdentityServerJwt 拡張メソッドを使用して、API の認証と承認を構成します。 認証ありの React または Angular SPA プロジェクト テンプレートを使用するプロジェクトには、このパッケージへの参照が含まれます。

Startup クラスには次のものが追加されます。

  • Startup.ConfigureServices メソッド内に、次のものがあります。

    • 既定の UI を使用する Identity:

      services.AddDbContext<ApplicationDbContext>(options =>
          options.UseSqlite(Configuration.GetConnectionString("DefaultConnection")));
      
      services.AddDefaultIdentity<ApplicationUser>()
          .AddEntityFrameworkStores<ApplicationDbContext>();
      
    • IdentityServer には、IdentityServer の上にいくつかの既定の ASP.NET Core 規則を設定する追加の AddApiAuthorization ヘルパー メソッドがあります。

      services.AddIdentityServer()
          .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
      
    • IdentityServer によって生成された JWT トークンを検証するようにアプリを構成する AddIdentityServerJwt ヘルパー メソッドが追加された Authentication。

      services.AddAuthentication()
          .AddIdentityServerJwt();
      
  • Startup.Configure メソッド内に、次のものがあります。

    • 要求の資格情報の検証と、要求コンテキストでのユーザーの設定を行う認証ミドルウェア:

      app.UseAuthentication();
      
    • OpenID Connect エンドポイントを公開する IdentityServer ミドルウェア:

      app.UseIdentityServer();
      

警告

この記事では、接続文字列の使用方法について説明します。 ローカル データベースでは、ユーザーを認証する必要はありませんが、運用環境では、接続文字列認証用のパスワードが含まれる場合があります。 リソース所有者パスワード資格情報 (ROPC) は、運用データベースで回避する必要があるセキュリティ リスクです。 運用アプリでは、使用可能な最も安全な認証フローを使用する必要があります。 テスト環境または運用環境にデプロイされたアプリの認証の詳細については、「 安全な認証フロー」を参照してください。

Azure App Service on Linux

Linux での Azure App Service デプロイについては、Startup.ConfigureServices に発行者を明示的に指定します。

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme, 
    options =>
    {
        options.Authority = "{AUTHORITY}";
    });

前のコードでは、{AUTHORITY} プレースホルダーが、OpenID Connect 呼び出し時に使用する Authority です。

例:

options.Authority = "https://contoso-service.azurewebsites.net";

AddApiAuthorization

このヘルパー メソッドは、サポートされている構成を使用するように IdentityServer を構成します。 IdentityServer は、アプリのセキュリティの問題を処理するための強力で拡張可能なフレームワークです。 同時に、ほとんどの一般的なシナリオには必要のない複雑さが発生します。 そのため、使用開始時に適切であると考えられる一連の規則と構成オプションが用意されています。 認証のニーズが変わった場合でも、IdentityServer の全機能を活用して、ニーズに合わせて認証をカスタマイズできます。

AddIdentityServerJwt

ヘルパー メソッドでは、アプリに対するポリシー スキームが既定の認証ハンドラーとして構成されます。 そのポリシーは、Identity の URL 空間 "/Identity" のサブパスにルーティングされたすべての要求を Identity で処理できるように構成されています。 それ以外のすべての要求は、JwtBearerHandler で処理されます。 さらに、このメソッドは、<<ApplicationName>>API API リソースを既定のスコープ <<ApplicationName>>API で IdentityServer に登録し、アプリ用に IdentityServer によって発行されたトークンを検証するように JWT ベアラー トークン ミドルウェアを構成します。

WeatherForecastController

このファイルでは、[Authorize] 属性がクラスに適用されていることに注意してください。これは、リソースにアクセスするための既定のポリシーに基づいてユーザーを承認する必要があることを示します。 既定の承認ポリシーは、前に説明したポリシー スキームに対して AddIdentityServerJwt によって設定される既定の認証スキームを使用するように構成され、このようなヘルパー メソッドによって構成された JwtBearerHandler は、アプリに対する要求の既定のハンドラーになります。

ApplicationDbContext

このファイルでは、同じ DbContext が Identity で使用されていることに注意してください。ただし、それが ApiAuthorizationDbContext (IdentityDbContext から派生したクラス) を拡張して、IdentityServer のスキーマを含める場合を除きます。

データベース スキーマを完全に制御するには、使用可能な IdentityDbContext クラスのいずれかを継承し、OnModelCreatingbuilder.ConfigurePersistedGrantContext(_operationalStoreOptions.Value) を呼び出すことで、Identity を含むようにコンテキストを構成します。

OidcConfigurationController

このファイルでは、クライアントが使用する必要のある OIDC パラメーターを提供するためにプロビジョニングされるエンドポイントに注意してください。

appsettings.json

プロジェクト ルートにある appsettings.json ファイルに、構成されているクライアントの一覧が記述されている新しい IdentityServer セクションがあります。 次の例には、1 つのクライアントがあります。 クライアント名はアプリケーション名に対応し、規則によって OAuth の ClientId パラメーターにマップされます。 構成対象のアプリの種類は、プロファイルによって示されています。 サーバーの構成プロセスを簡素化する規則を促進するために、内部的に使用されます。 「アプリケーション プロファイル」セクションで説明されているように、使用可能なプロファイルがいくつかあります。

"IdentityServer": {
  "Clients": {
    "angularindividualpreview3final": {
      "Profile": "IdentityServerSPA"
    }
  }
}

appsettings.Development.json

プロジェクト ルートの appsettings.Development.json ファイルには、トークンの署名に使用されるキーについて説明している IdentityServer セクションがあります。 運用環境にデプロイする場合は、「運用環境へのデプロイ」セクションで説明されているように、アプリと共にキーをプロビジョニングしてデプロイする必要があります。

"IdentityServer": {
  "Key": {
    "Type": "Development"
  }
}

Angular アプリの一般的な説明

Angular テンプレートでの認証と API 承認のサポートは、独自の Angular モジュールの ClientApp/src/api-authorization ディレクトリにあります。 モジュールは、次の要素で構成されています。

  • 3 つのコンポーネント:
    • login.component.ts: アプリのログイン フローを処理します。
    • logout.component.ts: アプリのログアウト フローを処理します。
    • login-menu.component.ts: 次のリンクのセットのいずれかを表示するウィジェット:
      • ユーザーが認証された場合のユーザー プロファイル管理とログアウトのリンク。
      • ユーザーが認証されていない場合の登録とログインのリンク。
  • ルートに追加でき、ルートにアクセスする前にユーザーを認証する必要があるルート セーフガード AuthorizeGuard
  • ユーザーが認証されるときに、API を対象とする発信 HTTP 要求にアクセス トークンを関連付ける HTTP インターセプター AuthorizeInterceptor
  • 認証プロセスの下位レベルの詳細を処理し、認証されたユーザーに関する情報をアプリの rest に公開して使用するサービス AuthorizeService
  • アプリの認証部分に関連付けられているルートを定義する Angular モジュール。 ログイン メニュー コンポーネント、インターセプター、セーフガード、サービスをアプリの rest から使用するために公開します。

React アプリの一般的な説明

React テンプレートでの認証と API 承認のサポートは、ClientApp/src/components/api-authorization ディレクトリにあります。 次の要素で構成されています。

  • 4 つのコンポーネント:
    • Login.js: アプリのログイン フローを処理します。
    • Logout.js: アプリのログアウト フローを処理します。
    • LoginMenu.js: 次のリンクのセットのいずれかを表示するウィジェット:
      • ユーザーが認証された場合のユーザー プロファイル管理とログアウトのリンク。
      • ユーザーが認証されていない場合の登録とログインのリンク。
    • AuthorizeRoute.js: Component パラメーターに示されているコンポーネントを表示する前に、ユーザーの認証を必要とするルート コンポーネント。
  • 認証プロセスの下位レベルの詳細を処理し、認証されたユーザーに関する情報をアプリの rest に公開して使用する、クラス AuthorizeService のエクスポートされた authService インスタンス。

これで、ソリューションの主要なコンポーネントを確認できました。次は、アプリの個々のシナリオについて詳しく見ていきましょう。

新しい API での承認が必要

既定では、システムは新しい API の承認を要求するように構成されています。 これを行うには、新しいコントローラーを作成し、コントローラー クラスまたはコントローラー内の任意のアクションに [Authorize] 属性を追加します。

API 認証ハンドラーをカスタマイズする

API の JWT ハンドラーの構成をカスタマイズするには、その JwtBearerOptions インスタンスを構成します。

services.AddAuthentication()
    .AddIdentityServerJwt();

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        ...
    });

API の JWT ハンドラーは、JwtBearerEvents を使用して認証プロセスを制御できるようにするイベントを発生させます。 API 承認のサポートを提供するために、AddIdentityServerJwt は独自のイベント ハンドラーを登録します。

イベントの処理をカスタマイズするには、必要に応じて追加のロジックを使用して既存のイベント ハンドラーをラップします。 次に例を示します。

services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        var onTokenValidated = options.Events.OnTokenValidated;       

        options.Events.OnTokenValidated = async context =>
        {
            await onTokenValidated(context);
            ...
        }
    });

前のコードでは、OnTokenValidated イベント ハンドラーはカスタム実装に置き換えられています。 この実装では、次のことを実行します。

  1. API 承認のサポートによって提供される元の実装を呼び出します。
  2. 独自のカスタム ロジックを実行します。

クライアント側のルートを保護する (Angular)

クライアント側ルートの保護は、ルートを構成するときに実行するセーフガードのリストに承認セーフガードを追加することによって行われます。 例として、メイン アプリ Angular モジュール内で fetch-data ルートがどのように構成されているかを確認できます。

RouterModule.forRoot([
  // ...
  { path: 'fetch-data', component: FetchDataComponent, canActivate: [AuthorizeGuard] },
])

ルートを保護しても実際のエンドポイントは保護されず (まだ [Authorize] 属性が適用されている必要があります)、ユーザーが認証されていないときに、特定のクライアント側ルートに移動できないようにするのみであることに注意する必要があります。

API 要求の認証 (Angular)

アプリと共にホストされる API に対する要求の認証は、アプリによって定義された HTTP クライアント インターセプターを使用することによって自動的に行われます。

クライアント側のルートを保護する (React)

プレーン Route コンポーネントの代わりに AuthorizeRoute コンポーネントを使用して、クライアント側のルートを保護します。 たとえば、App コンポーネント内で fetch-data ルートがどのように構成されているかに注目してください。

<AuthorizeRoute path='/fetch-data' component={FetchData} />

ルートの保護:

  • 実際のエンドポイントを保護しません (まだ [Authorize] 属性が適用されている必要があります)。
  • ユーザーが認証されていないときに、特定のクライアント側ルートに移動できないようにします。

API 要求の認証 (React)

React を使用した要求の認証は、まず AuthorizeService から authService インスタンスをインポートすることによって行われます。 アクセス トークンは authService から取得され、次に示すように要求にアタッチされます。 React コンポーネントでは、通常、この作業は componentDidMount ライフサイクル メソッドで実行されるか、ユーザー操作の結果として行われます。

authService をコンポーネントにインポートする

import authService from './api-authorization/AuthorizeService'

アクセス トークンを取得して応答にアタッチする

async populateWeatherData() {
  const token = await authService.getAccessToken();
  const response = await fetch('api/SampleData/WeatherForecasts', {
    headers: !token ? {} : { 'Authorization': `Bearer ${token}` }
  });
  const data = await response.json();
  this.setState({ forecasts: data, loading: false });
}

運用環境への配置

アプリを運用環境にデプロイするには、次のリソースをプロビジョニングする必要があります。

  • Identity ユーザー アカウントと IdentityServer の権限を保存するデータベース。
  • トークンの署名に使用する運用証明書。
    • この証明書には特定の要件はありません。自己署名証明書、または CA 証明機関を通じてプロビジョニングされた証明書を使用できます。
    • PowerShell や OpenSSL などの標準ツールを使用して生成できます。
    • ターゲット コンピューターの証明書ストアにインストールすることも、強力なパスワードを使用して .pfx ファイルとして展開することもできます。

例: Azure 以外の Web ホスティング プロバイダーへの展開

Web ホスティング パネルで、ご自分の証明書を作成または読み込みます。 次に、アプリの appsettings.json ファイルで、IdentityServer セクションを変更してキーの詳細を含めます。 次に例を示します。

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "WebHosting",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}

前の例の場合:

  • StoreName は、証明書が格納されている証明書ストアの名前を表します。 この例では、Web ホスティング ストアを指しています。
  • StoreLocation は、証明書の読み込み元を表します (この場合、CurrentUser)。
  • Name は、証明書の識別サブジェクトに対応します。

例: Azure App Service にデプロイする

このセクションでは、証明書ストアに格納されている証明書を使用した、Azure App Service へのアプリのデプロイについて説明します。 証明書ストアから証明書を読み込むようにアプリを変更するには、後の手順で、Azure portal でアプリを構成するときに、Standard レベル以上のサービス プランが必要です。

アプリの appsettings.json ファイルで、IdentityServer セクションを変更してキーの詳細を含めます。

"IdentityServer": {
  "Key": {
    "Type": "Store",
    "StoreName": "My",
    "StoreLocation": "CurrentUser",
    "Name": "CN=MyApplication"
  }
}
  • ストア名は、証明書が格納されている証明書ストアの名前を表します。 この場合、個人ユーザー ストアを指しています。
  • ストアの場所は、証明書の読み込み元を表します (この場合、CurrentUser または LocalMachine)。
  • 証明書の name プロパティは、証明書の識別サブジェクトに対応します。

Azure App Service にデプロイするには、「Azure にアプリを配置する」の手順に従います。ここでは、必要な Azure リソースを作成し、アプリを運用環境にデプロイする方法を説明します。

前述の手順に従った後、アプリは Azure にデプロイされますが、まだ機能していません。 アプリによって使用される証明書は、Azure portal で構成する必要があります。 証明書の拇印を見つけて、証明書の読み込みに関するページで説明されている手順に従います。

これらの手順では SSL について説明していますが、Azure portal には、アプリで使用するプロビジョニング済みの証明書をアップロードできるプライベート証明書に関するセクションがあります。

Azure portal でアプリとアプリの設定を構成した後、ポータルでアプリを再起動します。

その他の構成オプション

API 承認のサポートは、SPA のエクスペリエンスを簡素化するために一連の規則、既定値、および拡張機能を備えた IdentityServer 上に構築されます。 言うまでもなく、ASP.NET Core 統合がシナリオをカバーしない場合は、IdentityServer の全機能をバックグラウンドで利用できます。 ASP.NET Core サポートでは、すべてのアプリが組織によって作成およびデプロイされる "ファーストパーティ" アプリに重点を置いています。 そのため、同意やフェデレーションなどのサポートは提供されていません。 このようなシナリオでは、IdentityServerを使用し、そのドキュメントに従ってください。

アプリケーション プロファイル

アプリケーション プロファイルは、そのパラメーターをさらに定義するアプリの事前定義された構成です。 現時点では、次のプロファイルがサポートされています。

  • IdentityServerSPA: IdentityServer と共にホストされる SPA を 1 つの単位として表します。
    • redirect_uri の既定値は /authentication/login-callback です。
    • post_logout_redirect_uri の既定値は /authentication/logout-callback です。
    • スコープのセットには、openidprofile、アプリ内の API に対して定義されているすべてのスコープが含まれます。
    • 許可される OIDC 応答の種類のセットには、id_token token があります。またはそれぞれ個別に指定します (id_tokentoken)。
    • 許可される応答モードは fragment です。
  • SPA: IdentityServer でホストされていない SPA を表します。
    • スコープのセットには、openidprofile、アプリ内の API に対して定義されているすべてのスコープが含まれます。
    • 許可される OIDC 応答の種類のセットには、id_token token があります。またはそれぞれ個別に指定します (id_tokentoken)。
    • 許可される応答モードは fragment です。
  • IdentityServerJwt: IdentityServer と一緒にホストされている API を表します。
    • アプリは、アプリ名を既定とする 1 つのスコープを持つように構成されています。
  • API: IdentityServer でホストされていない API を表します。
    • アプリは、アプリ名を既定とする 1 つのスコープを持つように構成されています。

AppSettings を使用した構成

構成システムを使用して、Clients または Resources の一覧にアプリを追加してそれらを構成します。

次の例で示すように、各クライアントの redirect_uri および post_logout_redirect_uri プロパティを構成します。

"IdentityServer": {
  "Clients": {
    "MySPA": {
      "Profile": "SPA",
      "RedirectUri": "https://www.example.com/authentication/login-callback",
      "LogoutUri": "https://www.example.com/authentication/logout-callback"
    }
  }
}

リソースを構成するときは、次に示すようにリソースのスコープを構成できます。

"IdentityServer": {
  "Resources": {
    "MyExternalApi": {
      "Profile": "API",
      "Scopes": "a b c"
    }
  }
}

コードを使用した構成

また、オプションを構成するためのアクションを実行する AddApiAuthorization のオーバーロードを使用して、コードによってクライアントとリソースを構成することもできます。

AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
    options.Clients.AddSPA(
        "My SPA", spa =>
        spa.WithRedirectUri("http://www.example.com/authentication/login-callback")
           .WithLogoutRedirectUri(
               "http://www.example.com/authentication/logout-callback"));

    options.ApiResources.AddApiResource("MyExternalApi", resource =>
        resource.WithScopes("a", "b", "c"));
});

その他の技術情報