ASP.NET Web API での基本認証

作成者: Mike Wasson

基本認証は RFC 2617、HTTP 認証: 基本およびダイジェスト アクセス認証で定義されています。

短所

  • 要求内ではユーザー資格情報が送信されます。
  • 資格情報はプレーンテキストとして送信されます。
  • 資格情報は、すべての要求で送信されます。
  • ブラウザー セッションを終了する以外には、ログアウトする方法はありません。
  • クロスサイト リクエスト フォージェリ (CSRF) に対して脆弱であり、CSRF 対策が必要です。

長所

  • インターネット標準。
  • すべての主要なブラウザーでサポートされています。
  • 比較的単純なプロトコル。

基本認証は、次のように動作します。

  1. 要求に認証が必要な場合、サーバーは 401 (未承認) を返します。 応答には WWW-Authenticate ヘッダーが含まれ、サーバーが基本認証をサポートすることを示します。
  2. クライアントは、Authorization ヘッダーにクライアント資格情報を含む別の要求を送信します。 資格情報は、base64 でエンコードされた文字列 "name:password" として書式設定されます。 資格情報は暗号化されません。

基本認証は、"realm" のコンテキスト内で実行されます。サーバーは、WWW-Authenticate ヘッダー内に realm の名前を含めます。 ユーザーの資格情報は、その realm 内で有効です。 realm の正確なスコープは、サーバーによって定義されます。 たとえば、リソースをパーティション分割するために複数の realm を定義できます。

Diagram of basic authentication

資格情報は暗号化されずに送信されるため、基本認証が安全なのは HTTPS を介した場合だけです。 「Web API での SSL の操作」を参照してください。

基本認証は、CSRF 攻撃に対しても脆弱です。 ユーザーが資格情報を入力すると、セッションの間、ブラウザーは後続の要求時に自動的に同じドメインに資格情報を送信します。 これには AJAX 要求が含まれます。 「クロスサイト リクエスト フォージェリ (CSRF) 攻撃の防止」を参照してください。

IIS を使用した基本認証

IIS では基本認証がサポートされていますが、ユーザーは自身の Windows 資格情報に対して認証されるという注意事項があります。 つまり、ユーザーはサーバーのドメイン上のアカウントを持っている必要があります。 一般向け Web サイトの場合は、通常、ASP.NET メンバーシップ プロバイダーに対して認証する必要があります。

IIS を使用する基本認証を有効にするには、以下のように ASP.NET プロジェクトの Web.config で認証モードを "Windows" に設定します。

<system.web>
    <authentication mode="Windows" />
</system.web>

このモードでは、IIS は Windows 資格情報を使用して認証を行います。 さらに、IIS で基本認証を有効にする必要があります。 IIS マネージャーで、[機能ビュー] に移動し、[認証] を選択して、基本認証を有効にします。

Image of I I S Manager dashboard

Web API プロジェクトで、認証が必要なコントローラー アクションのための [Authorize] 属性を追加します。

クライアントは、要求内に Authorization ヘッダーを設定することで自身を認証します。 ブラウザー クライアントは、この手順を自動的に実行します。 非ブラウザー クライアントは、ヘッダーを設定する必要があります。

カスタム メンバーシップを使用した基本認証

前述のように、IIS に組み込まれている基本認証は Windows 資格情報を使用します。 つまり、ホスティング サーバーでユーザーのアカウントを作成する必要があります。 しかし、インターネット アプリケーションの場合、ユーザー アカウントは通常、外部データベース内に保存されています。

次のコードは HTTP モジュールが基本認証をどのように実行するかを示しています。 この例ではダミー メソッドである CheckPassword メソッドを置き換えることで、ASP.NET メンバーシップ プロバイダーを簡単にプラグインできます。

Web API 2 では、HTTP モジュールの代わりに、認証フィルターまたは OWIN ミドルウェアの記述を検討するべきです。

namespace WebHostBasicAuth.Modules
{
    public class BasicAuthHttpModule : IHttpModule
    {
        private const string Realm = "My Realm";

        public void Init(HttpApplication context)
        {
            // Register event handlers
            context.AuthenticateRequest += OnApplicationAuthenticateRequest;
            context.EndRequest += OnApplicationEndRequest;
        }

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

        // TODO: Here is where you would validate the username and password.
        private static bool CheckPassword(string username, string password)
        {
            return username == "user" && password == "password";
        }

        private static void AuthenticateUser(string credentials)
        {
            try
            {
                var encoding = Encoding.GetEncoding("iso-8859-1");
                credentials = encoding.GetString(Convert.FromBase64String(credentials));

                int separator = credentials.IndexOf(':');
                string name = credentials.Substring(0, separator);
                string password = credentials.Substring(separator + 1);

                if (CheckPassword(name, password))
                {
                    var identity = new GenericIdentity(name);
                    SetPrincipal(new GenericPrincipal(identity, null));
                }
                else
                {
                    // Invalid username or password.
                    HttpContext.Current.Response.StatusCode = 401;
                }
            }
            catch (FormatException)
            {
                // Credentials were not formatted correctly.
                HttpContext.Current.Response.StatusCode = 401;
            }
        }

        private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
        {
            var request = HttpContext.Current.Request;
            var authHeader = request.Headers["Authorization"];
            if (authHeader != null)
            {
                var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

                // RFC 2617 sec 1.2, "scheme" name is case-insensitive
                if (authHeaderVal.Scheme.Equals("basic",
                        StringComparison.OrdinalIgnoreCase) &&
                    authHeaderVal.Parameter != null)
                {
                    AuthenticateUser(authHeaderVal.Parameter);
                }
            }
        }

        // If the request was unauthorized, add the WWW-Authenticate header 
        // to the response.
        private static void OnApplicationEndRequest(object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", Realm));
            }
        }

        public void Dispose() 
        {
        }
    }
}

HTTP モジュールを有効にするには、web.config ファイルの system.webServer セクションに以下を追加します。

<system.webServer>
    <modules>
      <add name="BasicAuthHttpModule" 
        type="WebHostBasicAuth.Modules.BasicAuthHttpModule, YourAssemblyName"/>
    </modules>

"YourAssemblyName" をアセンブリの名前に置き換えます ("dll" 拡張子は含みません)。

フォーム認証や Windows 認証など、他の認証スキームは無効にする必要があります。