ASP.NET Web API での基本認証
作成者: Mike Wasson
基本認証は RFC 2617、HTTP 認証: 基本およびダイジェスト アクセス認証で定義されています。
短所
- 要求内ではユーザー資格情報が送信されます。
- 資格情報はプレーンテキストとして送信されます。
- 資格情報は、すべての要求で送信されます。
- ブラウザー セッションを終了する以外には、ログアウトする方法はありません。
- クロスサイト リクエスト フォージェリ (CSRF) に対して脆弱であり、CSRF 対策が必要です。
長所
- インターネット標準。
- すべての主要なブラウザーでサポートされています。
- 比較的単純なプロトコル。
基本認証は、次のように動作します。
- 要求に認証が必要な場合、サーバーは 401 (未承認) を返します。 応答には WWW-Authenticate ヘッダーが含まれ、サーバーが基本認証をサポートすることを示します。
- クライアントは、Authorization ヘッダーにクライアント資格情報を含む別の要求を送信します。 資格情報は、base64 でエンコードされた文字列 "name:password" として書式設定されます。 資格情報は暗号化されません。
基本認証は、"realm" のコンテキスト内で実行されます。サーバーは、WWW-Authenticate ヘッダー内に realm の名前を含めます。 ユーザーの資格情報は、その realm 内で有効です。 realm の正確なスコープは、サーバーによって定義されます。 たとえば、リソースをパーティション分割するために複数の realm を定義できます。
資格情報は暗号化されずに送信されるため、基本認証が安全なのは 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 マネージャーで、[機能ビュー] に移動し、[認証] を選択して、基本認証を有効にします。
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 認証など、他の認証スキームは無効にする必要があります。