Visual Studio 2013 / 2015 の Web API テンプレートで、認証を「個別のユーザーアカウントカウント」として自動生成させた Web API アプリの認証は、デフォルトで、トークンを要求ヘッダに含めてサーバーに送信するトークンベースになります。(MVC テンプレートで作った場合は未確認です。Core 3.1 版の Web API については別の記事「ASP.NET Core Web API と JWT」に書きました)
上の画像は Web API に要求をかけたときの要求ヘッダを Fiddler で表示したものです。赤枠で示した部分が認証に使われるトークンです。(画像には認証クッキーも含まれていますが、その理由等については後述します)
(注:クッキーではなくトークンを使う理由はセキュリティ (CSRF) 対策だそうです。MVC アプリではビューに Html ヘルパーの AntiForgeryToken メソッドを、アクションメソッドに [ValidateAntiForgeryToken] 属性を付与して CSRF 対策ができますが、Web API ではそういう手段が使えませんから)
テンプレートで自動生成されたそのままの状態でアプリを実行してトークンが送られるわけではないです。トークンを送って認証が通るようにする手順と注意事項を以下に書いておきます。
トークンによる認証の仕組みについては Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 に詳しく書いてありますので、それを読むことをお勧めします。
その記事からリンクが張ってある GitHub のサイトから完全なサンプルが入手できます。それを試してもらってもいいのですが、そのサンプルには SSL 関係や knockout.js など Web API の認証とは直接関係のない部分もあって分かり難いところがあるかもしれません。(注:運用上は SSL の実装は必須です。knockout.js は必須ではありませんが)
なので、以下に Visual Studio 2015 の Web API テンプレート作成したアプリに、追加で何を実装すればトークンを入手でき、そしてトークンをどのように送信すれば認証が通るかだけを書いておきます。
事前に、ASP.NET Identity がユーザー情報を取得する LocalDB に email と password を登録し有効なユーザーアカウントを作成しておく必要があります。(その手順は本題とは直接関係ないので割愛します。手抜きでスミマセン)
トークンの入手は、ビューに以下のようなスクリプトを追加し、jQuery ajax を使って登録済みの email と password をトークンエンドポイント /Token に POST してやります。email と password が有効であれば、応答の JSON 文字列にトークンが含まれて戻ってきます。そのトークンを sessionStorage に保存します。
var tokenKey = 'accessToken';
function getToken() {
var email = document.getElementById("email").value;
var password = document.getElementById("password").value;
var loginData = {
grant_type: 'password',
username: email,
password: password
};
$.ajax({
type: "POST",
url: "/Token",
data: loginData,
success: function (data) {
sessionStorage.setItem(tokenKey, data.access_token);
},
error: function (jqXHR, textStatus, errorThrown) {
// ・・・中略・・・
}
});
}
トークンの送信は、上の getToken メソッドで sessionStorage に保存したトークンを取得し、以下のように要求ヘッダの Authorization に設定して送信してやります。それで認証が通って期待した応答が返ってきます。
function apiHeroesGet() {
var token = sessionStorage.getItem(tokenKey);
var headers = {};
if (token) {
headers.Authorization = 'Bearer ' + token;
}
$.ajax({
type: "GET",
url: "/api/values",
headers: headers,
success: function (data, textStatus, jqXHR) {
// ・・・中略・・・
},
error: function (jqXHR, textStatus, errorThrown) {
// ・・・中略・・・
}
});
}
主な話は以上ですが、追加で 2, 3 気になったことを書いておきます。
(1) トークンエンドポイント /Token
上のスクリプトの getToken メソッドの email と password の送信先 /Token は、Startup.Auth.cs の ConfigureAuth メソッドで OAuthAuthorizationServerOptions クラスを初期化するときに TokenEndpointPath プロパティに設定されます。
自動生成されたコードの中のアクションメソッドだと思って探したけれど、見つからないので焦ったというのは内緒です。(笑)
(2) 認証クッキーの発行
上の画像で認証クッキーも発行されていますが、それは上のスクリプトの getToken メソッドでトークンを発行する際に同時に発行されます。
デフォルトで、Startup.Auth.cs の ConfigureAuth メソッドには app.UseCookieAuthentication(...) メソッドが含まれていることに注意してください。
MVC 側のアクションメソッドの認証には、この認証クッキーが使用されます。例えば MVC 側のアクションメソッドに [Authorize] を付与してやると、認証クッキーなしでは 401 応答が返ってきます。
401 応答ではなく、302 応答を返してログインページにリダイレクトするには、引数の CookieAuthenticationOptions オブジェクトの LoginPath プロパティにログインページの URL を new PathString("/Account/Login") というように指定してやります。
(3) AuthenticationType の設定
UseCookieAuthentication メソッドの引数 CookieAuthenticationOptions オブジェクトの AuthenticationType を設定すると、トークンエンドポイント /Token に email と password を送信しても認証クッキーは発行されなくなります。(なお、トークンは発行されます)
認証クッキーは MVC 側でのログインだけで発行したい場合は、AuthenticationType を設定すればよさそうです。そのことが書いてあるドキュメントは見つけられなかったのですが、そのような目的のためにそうなっているような気がします。
AuthenticationType に設定されるのは単なる文字列で、何かの識別用に使っているらしいです。これをきちんと指定しないとログアウトできないなどの問題が出るという stackoverflow の記事を見かけました。(自分が Web API テンプレートのアプリで試した限りではそういう問題は出なかったですが・・・)
(4) トークンによる認証の強制
デフォルトでは Web API 側の認証はトークンになるのは、WebApiConfig.cs にある以下のコードによります。
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
OAuthDefaults.AuthenticationType));
前者のメソッドでホスト(IIS と MVC) による認証は無視され、Web API には匿名アクセスとなるらしいです。後者のメソッドで HostAuthenticationFilter によりベアラトークンによる認証が行われるようになるそうです。
ちなみに、上の 2 行をコメントアウトすると認証クッキーだけで認証が通ってしまうようになります。
詳しくは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring Web API to use Bearer Tokens のセクションに詳しく書いてありますので、そちらを見てください。