既存の ASP.MET MVC5 アプリに Web API 2.2 のコントローラを追加してみました。その方法を備忘録として書いておきます。需要はないかもしれませんが。(笑)

既存の MVC5 アプリは VS2015 のテンプレートで生成した .NET Framework v4.6.1 ベースの単独 MVC プロジェクトで、ASP.NET Identity を利用してクッキーベースの認証を行っています。
その既存の MVC5 アプリに Web API の機能を追加するのですが、Web API の認証は既存のクッキーベースとするのではなく、Web API で推奨されているトークンベースとします。その際、既存の MVC5 アプリが持つ ASP.NET Identity からユーザー情報を得てトークン認証を行うようにします。
基本的には、下の画像のように VS2015 の Web API テンプレートを使ってプロジェクトを新規に作り、それから必要な部分を既存の MVC プロジェクトに追加していくという感じです。

Web API プロジェクトを作った後、以下の手順で、既存の MVC プロジェクトに必要なパッケージ、コードを追加します。
(1) SSL 有効化
開発環境でも SSL 通信下で検証ができるように、先の記事「IIS Express で SSL 通信」に従って IIS Express で SSL 通信を利用できるように設定します。
(2) NuGet パッケージのインストール
既存の MVC プロジェクトに Web API 関係の NuGet パッケージをインストールします。必要なパッケージは、Web API テンプレートで自動生成されたプロジェクトの NuGet パッケージの管理画面で「インストール済み」の WebApi 関係のパッケージを表示すると分かります。自分の環境では以下の 6 つでした。
-
Microsoft.AspNet.WebApi
-
Microsoft.AspNet.WebApi.Client
-
Microsoft.AspNet.WebApi.Core
-
Microsoft.AspNet.WebApi.WebHost
-
Microsoft.AspNet.WebApi.Owin
-
Microsoft.AspNet.WebApi.HelpPage
Microsoft.AspNet.WebApi を選んでインストールすると自動的に Client, Core, WebHost もインストールされます。Owin, HelpPage はその後で追加インストールしました。
(3) RequireHttpsAttribute の追加
SSL 通信を強制するためのフィルター RequireHttpsAttribute を追加します。(基本的な動作には影響ないのですが、実装しておいた方がよさそうですので)
まず、MVC 側ですが、MVC 5.2 以降であれば System.Web.Mvc 名前空間に RequireHttpsAttribute Class が用意されていますので、それを FilterConfig.cs で以下のように追加します。
1 2 3 4 5 6 7 8 | public static void RegisterGlobalFilters(
GlobalFilterCollection filters)
{
filters.Add( new HandleErrorAttribute());
filters.Add( new RequireHttpsAttribute());
}
|
Web API 側では上記のフィルターは使えないので、カスタムフィルターを作ってそれを使うことになります。(MVC 用と Web API 用とではフィルターは違うので注意)
カスタムフィルターは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 からリンクが張ってある GitHub のページ からダウンロードできるプロジェクトの Filters フォルダにサンプルがありましたので、それを借用しました。
そのカスタムフィルター RequireHttpsAttribute.cs のコードをそのまま載せておきます。(名前空間は自分のプロジェクトに合わせて変更しました)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | using System;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;
namespace Mvc5.Filters
{
public class RequireHttpsAttribute :
AuthorizationFilterAttribute
{
public int Port { get ; set ; }
public RequireHttpsAttribute()
{
Port = 443;
}
public override void OnAuthorization(
HttpActionContext actionContext)
{
var request = actionContext.Request;
if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
{
var response = new HttpResponseMessage();
if (request.Method == HttpMethod.Get ||
request.Method == HttpMethod.Head)
{
var uri = new UriBuilder(request.RequestUri);
uri.Scheme = Uri.UriSchemeHttps;
uri.Port = this .Port;
response.StatusCode = HttpStatusCode.Found;
response.Headers.Location = uri.Uri;
}
else
{
response.StatusCode = HttpStatusCode.Forbidden;
}
actionContext.Response = response;
}
else
{
base .OnAuthorization(actionContext);
}
}
}
}
|
このカスタムフィルターを有効にする方法は下の「(4) WebApiConfig.cs の追加」のセクションを見てください。
(4) WebApiConfig.cs の追加
Web API プロジェクトから WebApiConfig.cs をコピーして MVC プロジェクトの App_Start フォルダにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。
Register メソッドに、上の「(3) RequireHttpsAttribute の追加」のセクションで用意した Web API 用のカスタムフィルターを有効化するため、以下のコードを追加します。
1 2 3 4 5 6 7 | public static void Register(HttpConfiguration config)
{
config.Filters.Add( new Mvc5.Filters.RequireHttpsAttribute());
}
|
その後、WebApiConfig.Register を Global.asax の Application_Start メソッドに登録します。
1 2 3 4 5 6 7 | protected void Application_Start()
{
GlobalConfiguration.Configure(WebApiConfig.Register);
}
|
(5) UseOAuthBearerTokens 追加
トークン認証を有効にするため、Startup.Auth.cs の ConfigureAuth メソッドに以下のコードを追加します。コードは Web API テンプレートで作成したプロジェクトにありますので、それをコピーして修正すればいいです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | public partial class Startup
{
public static OAuthAuthorizationServerOptions
OAuthOptions { get ; private set ; }
public static string PublicClientId { get ; private set ; }
public void ConfigureAuth(IAppBuilder app)
{
PublicClientId = "self" ;
OAuthOptions = new OAuthAuthorizationServerOptions
{
TokenEndpointPath = new PathString( "/Token" ),
Provider = new ApplicationOAuthProvider(PublicClientId),
AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),
AllowInsecureHttp = false
};
app.UseOAuthBearerTokens(OAuthOptions);
|
AllowInsecureHttp プロパティは SSL 通信強制のため false に設定してください。
ApplicationOAuthProvider は Web API テンプレートで作成したプロジェクトの Providers フォルダにあるものをコピーして使います。詳しくは下の「(6) ApplicationOAuthProvider 追加」セクションを見てください。
AuthorizeEndpointPath プロパティについては、stackoverflow の記事 What is AuthorizeEndpointPath? に説明があります。
ユーザーがクレデンシャルを入力してトークンを得るという条件に限定すれば、AuthorizeEndpointPath プロパティの設定はコメントアウトしても問題なさそうです。(それで 100% 問題ないと言い切れる自信はないですが)
(6) ApplicationOAuthProvider 追加
Web API テンプレートで作成したプロジェクトの Providers フォルダにある ApplicationOAuthProvider.cs をフォルダごと MVC プロジェクトにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。
GrantResourceOwnerCredentials メソッドの中で使用されている第 2 引数に string を持つ GenerateUserIdentityAsync メソッドは、Web API プロジェクトの IdentityModel.cs に定義されているものをコピーして、MVC プロジェクトの IdentityModel.cs にペーストしてください。
このプロバイダは、OWIN ミドルウェアのプラグインとして、OWIN ミドルウェアで発生するイベントを処理するためのものだそうです。詳しくは Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring the Authorization Server のセクションを見てください。
(7) トークン取得・削除のスクリプト
トークン取得・削除のスクリプトは以下のコードのようにします。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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) {
}
});
}
function removeToken() {
sessionStorage.removeItem(tokenKey);
}
|
以上で MVC 側はクッキーベースで、Web API 側はトークンベースで独立して認証が働きます。それを可能にしているのは WebApiConfig.cs に含まれている以下の 2 行です。
1 2 3 | config.SuppressDefaultHostAuthentication();
config.Filters.Add( new HostAuthenticationFilter(
OAuthDefaults.AuthenticationType));
|
この 2 行をコメントアウトすると Web API 側もクッキーベースの認証となります。なお、その際 Web API に匿名アクセスすると 401 応答ではなく、以下のように 200 応答となりますので注意してください。
