ASP.NET Web API の認証と承認

作成者: Rick Anderson

Web API を作成しましたが、その Web API へのアクセスを制御する必要があります。 この一連の記事では、承認されていないユーザーから Web API をセキュリティで保護するためのいくつかのオプションについて説明します。 このシリーズでは、認証と承認の両方について説明します。

  • 認証は、ユーザーの ID を認識することです。 たとえば、Alice は自分のユーザー名とパスワードを使用してログインし、サーバーはそのパスワードを使用して Alice を認証します。
  • 承認は、ユーザーがアクションを実行できるかどうかを決定することです。 たとえば、Alice にはリソースを取得するアクセス許可がありますが、リソースを作成するアクセス許可はありません。

シリーズの最初の記事では、ASP.NET Web API での認証と承認の概要について説明します。 その他のトピックでは、Web API の一般的な認証シナリオについて説明します。

Note

このシリーズをレビューし、貴重なフィードバックを提供してくれた Rick Anderson、Levi Broderick、Barry Dorrans、Tom Dykstra、Hongmei Ge、David Matson、Daniel Roth、Tim Teebken に感謝します。

認証

Web API は、ホストで認証が行われることを前提としています。 Web ホスティングの場合ホストは IIS であり、ここでは、認証に HTTP モジュールが使用されます。 IIS または ASP.NET に組み込まれている認証モジュールのいずれかを使用するようにプロジェクトを構成することも、カスタム認証を実行する独自の HTTP モジュールを作成することもできます。

ホストがユーザーを認証すると、プリンシパルが作成されます。これは、コードが実行されているセキュリティ コンテキストを表す IPrincipal オブジェクトです。 ホストは、Thread.CurrentPrincipal を設定して、現在のスレッドにプリンシパルをアタッチします。 プリンシパルには、ユーザーに関する情報を含む関連付けられた ID オブジェクトが含まれています。 ユーザーが認証されると、Identity.IsAuthenticated プロパティは true を返します。 匿名要求の場合、IsAuthenticatedfalse を返します。 プリンシパルの詳細については、「ロール ベース セキュリティ」を参照してください。

認証用の HTTP メッセージ ハンドラー

認証にホストを使用する代わりに、認証ロジックを HTTP メッセージ ハンドラーに含められます。 その場合、メッセージ ハンドラーは HTTP 要求を調べ、プリンシパルを設定します。

どのような場合に認証にメッセージ ハンドラーを使用する必要があるでしょうか。 いくつかのトレードオフを次に示します。

  • HTTP モジュールは、ASP.NET パイプラインを通過するすべての要求を確認します。 メッセージ ハンドラーは、Web API にルーティングされた要求のみを確認します。
  • ルートごとのメッセージ ハンドラーを設定できます。これにより、特定のルートに認証スキームを適用できます。
  • HTTP モジュールは IIS に固有です。 メッセージ ハンドラーはホストに依存しないため、Web ホスティングとセルフホスティングの両方で使用できます。
  • HTTP モジュールは、IIS のログ記録や監査などに参加します。
  • HTTP モジュールは、パイプラインの早い段階で実行されます。 メッセージ ハンドラーで認証を処理する場合、プリンシパルはハンドラーが実行されるまで設定されません。 さらに、応答がメッセージ ハンドラーから離れると、プリンシパルは前のプリンシパルに戻ります。

一般に、セルフホスティングをサポートする必要がない場合は、HTTP モジュールを使用することをお勧めします。 セルフホスティングをサポートする必要がある場合は、メッセージ ハンドラーを検討してください。

プリンシパルの設定

アプリケーションでカスタム認証ロジックを実行する場合は、プリンシパルを次の 2 つの場所に設定する必要があります。

  • Thread.CurrentPrincipal。 このプロパティは、.NET でスレッドのプリンシパルを設定する標準的な方法です。
  • HttpContext.Current.User。 このプロパティは、ASP.NET に固有です。

次のコードは、プリンシパルを設定する方法を示しています。

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

Web ホスティングの場合は、両方の場所でプリンシパルを設定する必要があります。そうしないと、セキュリティ コンテキストが不整合になる可能性があります。 ただし、セルフホスティングの場合、HttpContext.Current は null です。 そのため、コードがホストに依存しないようにするには、例に示すように、HttpContext.Current に割り当てる前に null を確認します。

承認

承認は、パイプラインの後の、コントローラーの近くで行われます。 これにより、リソースへのアクセスを許可するときに、より詳細な選択を行うことができます。

  • 承認フィルターは、コントローラー アクションの前に実行されます。 要求が承認されていない場合、フィルターはエラー応答を返し、アクションは呼び出されません。
  • コントローラー アクション内で、ApiController.User プロパティから現在のプリンシパルを取得できます。 たとえば、ユーザー名に基づいてリソースの一覧をフィルター処理し、そのユーザーに属するリソースのみを返す場合があります。

Diagram of the authentication and authorization pipeline.

[Authorize] 属性の使用

Web API には、組み込みの承認フィルター AuthorizeAttribute が用意されています。 このフィルターは、ユーザーが認証されているかどうかを確認します。 そうでない場合は、アクションを呼び出さずに HTTP 状態コード 401 (許可されていません) が返されます。

フィルターは、グローバル、コントローラー レベル、または個々のアクションのレベルで適用できます。

グローバル: すべての Web API コントローラーのアクセスを制限するには、AuthorizeAttribute フィルターをグローバル フィルターの一覧に追加します。

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

コントローラー: 特定のコントローラーのアクセスを制限するには、フィルターを属性としてコントローラーに追加します。

// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

アクション: 特定のアクションのアクセスを制限するには、アクション メソッドに属性を追加します。

public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

または、[AllowAnonymous] 属性を使用して、コントローラーを制限し、特定のアクションへの匿名アクセスを許可することもできます。 次の例では、Post メソッドは制限されますが、Get メソッドは匿名アクセスを許可します。

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}

前の例では、フィルターを使用すると、認証されたユーザーは制限されたメソッドにアクセスできます。匿名ユーザーのみが除外されます。特定のユーザーまたは特定のロールのユーザーにアクセスを制限することもできます。

// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

Note

Web API コントローラーの AuthorizeAttribute フィルターは、System.Web.Http 名前空間にあります。 System.Web.Mvc 名前空間には MVC コントローラー用の同様のフィルターがあり、Web API コントローラーと互換性がありません。

カスタム承認フィルター

カスタム承認フィルターを作成するには、次のいずれかの種類から派生します。

  • AuthorizeAttribute。 このクラスを拡張して、現在のユーザーとユーザーのロールに基づいて承認ロジックを実行します。
  • AuthorizationFilterAttribute。 このクラスを拡張して、現在のユーザーまたはロールに基づくとは限らない同期承認ロジックを実行します。
  • IAuthorizationFilter。 非同期承認ロジックを実行するには、このインターフェイスを実装します。たとえば、承認ロジックが非同期の I/O またはネットワーク呼び出しを行う場合などです。 (承認ロジックが CPU にバインドされている場合は、AuthorizationFilterAttribute から派生する方が簡単です。このようにすることで、非同期メソッドを記述する必要がないためです)。

次の図は、AuthorizeAttribute クラスのクラス階層を示しています。

Diagram of the class hierarchy for the Authorize Attribute class.

AuthorizeAttribute クラスのクラス階層の図。 AuthorizeAttribute は下部にあり、矢印が AuthorizationFilterAttribute を指し、矢印が上部の IAuthorizationFilter を指しています。

コントローラー アクション内での承認

場合によっては、要求の続行を許可しても、プリンシパルに基づいて動作を変更することがあります。 たとえば、返される情報は、ユーザーのロールによって変わる場合があります。 コントローラー メソッド内では、ApiController.User プロパティから現在のプリンシパルを取得できます。

public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}