ASP.NET Core MVC で Basic 認証

先日 ASP.NET Core で Basic 認証を行うサンプルを書いた。

tnakamura.hatenablog.com

ただ、これだと全てのパスで認証が必要になってしまう。 もともとやりたかったことは、 「ASP.NET Core MVC で Authorized 属性を付けたアクションだけを認証必須にすること」。

Microsoft.AspNetCore.Authentication をベースに Basic 認証ミドルウェアを作れば実現できそうだったけど、既に同じことをやっている人がいた。

github.com

NuGet でパッケージが公開されているので、使わせてもらうことにした。

using idunno.Authentication;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using System.IO;
using System.Security.Claims;
using System.Threading.Tasks;

namespace BasicAuthExample
{
    public class Program
    {
        public static void Main(string[] args)
        {
            var host = new WebHostBuilder()
                .UseKestrel()
                .UseContentRoot(Directory.GetCurrentDirectory())
                .UseIISIntegration()
                .UseStartup<Startup>()
                .UseApplicationInsights()
                .Build();

            host.Run();
        }
    }

    public class Startup
    {
        public Startup(IHostingEnvironment env)
        {
            var builder = new ConfigurationBuilder()
                .SetBasePath(env.ContentRootPath)
                .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
                .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true)
                .AddEnvironmentVariables();
            Configuration = builder.Build();
        }

        public IConfigurationRoot Configuration { get; }

        public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc();
        }

        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();

            // Basic 認証を使用する
            app.UseBasicAuthentication(new BasicAuthenticationOptions()
            {
                Realm = "BasicAuthExample",
                Events = new BasicAuthenticationEvents()
                {
                    OnValidateCredentials = context =>
                    {
                        // ユーザー名とパスワードはとりあえず固定
                        if (context.Username == "tnakamura" &&
                            context.Password == "test12345")
                        {
                            var claims = new Claim[]
                            {
                                new Claim(ClaimTypes.Name, context.Username),
                            };
                            var identity = new ClaimsIdentity(claims, context.Options.AuthenticationScheme);
                            var principal = new ClaimsPrincipal(identity);
                            context.Ticket = new AuthenticationTicket(
                                principal,
                                new AuthenticationProperties(),
                                context.Options.AuthenticationScheme);
                        }
                        return Task.FromResult(true);
                    },
                }
            });

            app.UseMvcWithDefaultRoute();
        }
    }

    public class HomeController : Controller
    {
        [HttpGet("/")]
        public string Index()
        {
            return "Hello World!";
        }

        // アクセスすると認証ダイアログが表示される
        [Authorize]
        [HttpGet("/hello")]
        public string Hello()
        {
            return $"Hello {User.Identity.Name}!";
        }
    }
}

これで、Authorize 属性が付いたアクションの呼び出しに Basic 認証が効くようになった。 今回もユーザー名とパスワードは固定なので、ここはデータベースから取得した方が良いだろう。

OnValidateCredentials に登録するデリゲートでは、context.HttpContext.RequestServices を使って DI コンテナに登録した DbContext や自作リポジトリなんかを取得できるので、実装するのは難しくないはず。