Cutting Edge

ASP.NET Identity の概要

Dino Esposito

Dino EspositoVisual Studio 2013 と共に登場した "1 つの ASP.NET" Web 開発手法からは、ASP.NET Identity という新しいシステムが生まれました。ASP.NET Identity は、Web フォーム ベースと MVC ベースの両方の ASP.NET アプリケーションについて推奨のユーザー認証処理方法です。今回のコラムでは、ASP.NET 認証の基本を再確認し、ASP.NET MVC 5 開発者の視点から新しい ASP.NET Identity システムについて説明します。

長年 ASP.NET では、Windows 認証とフォーム認証という 2 種類の基本的な認証をサポートしてきました。Windows 認証を公開 Web サイトで実用的に使用できることはめったにありません。これは、Windows 認証が Windows アカウントとアクセス制御リスト (ACL) トークンに基づいているためです。つまり、ユーザーがアプリケーションのドメインに Windows アカウントを持っている必要があるうえ、クライアントの接続元には Windows を実行しているコンピューターが想定されます。もう一方のフォーム認証は、広く採用されている手法です。フォーム認証は単純な考え方に基づいています。フォーム認証を使用するアプリケーションでは、保護されているリソースにアクセスするたびに、要求に有効な認証 Cookie が含まれていることを確認します。有効な Cookie が見つかった場合は、通常どおり要求に対処します。見つからなかった場合は、ユーザーをログイン ページにリダイレクトし、資格情報の入力をユーザーに求めます。ユーザーの資格情報が有効と認められれば、特定の有効期限ポリシーが設定された認証 Cookie をアプリケーションから発行します。単純で効果的な方法です。

フォーム認証モジュールを実装するには、ユーザーの資格情報を収集してその情報を既知のユーザーのデータベースに照らしてチェックする、独立したモジュールが必要です。このようなメンバーシップ サブシステムの作成は開発チームが担う主な役割の 1 つですが、最もわずらわしい作業の 1 つでもあります。メンバーシップ システムの作成自体は難しくありません。ほとんどの場合は、ある種のストレージ システムに対してクエリを実行し、ユーザー名とパスワードを確認する必要があります。この処理は定型コードです。しかも、パスワードの変更と回復、絶えず変化するオンライン ユーザー数の処理など、新しい認証機能を追加していくとコードのサイズが巨大になることがあります。さらに、ストレージの構造を変更する場合やユーザーを説明するオブジェクトに情報を追加する場合は、ほとんどゼロからコードを書き直す必要があります。2005 年にリリースした ASP.NET 2.0 で、マイクロソフトはこの問題の対策として、プロバイダー ベースのアーキテクチャとメンバーシップ プロバイダーを ASP.NET フレームワークに導入しました。この結果、毎回最初からコードを書き直す必要がなくなり、組み込みシステムのメンバーシップを派生して、変更が必要な機能だけをオーバーライドすればよくなりました。

ASP.NET のネイティブなメンバーシップ プロバイダーは、コントラクトが定義されたインターフェイスを公開するスタンドアロン コンポーネントです。認証プロセスを調整する ASP.NET ランタイムは、メンバーシップ インターフェイスを認識しており、アプリケーションのメンバーシップ プロバイダーとして構成された任意のコンポーネントを呼び出せます。ASP.NET には、新しい特定のデータベース スキーマに基づいた既定のメンバーシップ プロバイダーが付属しています。しかし、簡単に独自のメンバーシップ プロバイダーを作成して、基本的な参照先を別のデータベース (通常はユーザーの既存のデータベース) に設定することもできます。

すばらしいアーキテクチャだと思いますか。導入当初は、ほとんどだれもがそう思っていました。しかし、時間がたつにつれ、カスタム メンバーシップ プロバイダーを何度も構築しようとした多数の開発者が、インターフェイスの冗長さに不満を訴えるようになりました。実際に、メンバーシップ プロバイダーは MembershipProvider という継承可能な基本クラスとして定義されていますが、このクラスのメンバーのうち 30 以上が抽象メンバーとしてマークされています。つまり、新しいメンバーシップ プロバイダーを作成するたびに、少なくとも 30 のメンバーをオーバーライドする必要がありました。しかも、その多くはほとんどの場合、本当には必要ありませんでした。もっと簡単なメンバーシップ アーキテクチャが必要とされていました。

簡易なメンバーシップ プロバイダーを導入する

カスタム メンバーシップ層をまったくのゼロから作成する手間を省くために、マイクロソフトは Visual Studio 2010 SP1 で簡易なメンバーシップ API という新しいオプションを導入しました。簡易なメンバーシップ API は、もともとは WebMatrix と Web ページで使用できた機能ですが、認証の管理手段として、とりわけ ASP.NET MVC で広く普及しました。特に、ASP.NET MVC 4 のインターネット アプリケーション テンプレートは、ユーザーの管理と認証をサポートするために簡易なメンバーシップ API を使用しています。

この API の内部を調べると、API が従来の ASP.NET メンバーシップ API とその SQL Server ベース データ ストアに対するラッパーでしかないことがわかります。簡易なメンバーシップを使用すると開発者は任意のデータ ストアを操作でき、ユーザー名やユーザー ID として機能するテーブル内の列を指定するだけで済みます。

従来のメンバーシップ API との大きな違いは、すべてのメソッドのパラメーター数が激減したことです。また、メンバーシップ ストレージのスキーマに関する限り、自由度が大幅に向上しました。簡易な API の例として、新しいユーザーの作成に必要な作業について考えてみましょう。

 

WebSecurity.CreateUserAndAccount(username, password,
  new { FirstName = fname, LastName = lname, Email = email });

メンバーシップに関する処理の大半には、WebSecurity クラスを使用します。ただし、ASP.NET MVC 4 の WebSecurity クラスでは、従来のメンバーシップ プロバイダーではなく拡張されたメンバーシップ プロバイダーを操作すると想定しています。拡張されたメンバーシップ プロバイダーに追加されている機能は、OAuth アカウントの処理に関する機能です。したがって、ASP.NET MVC 4 には 2 つのメンバーシップ実装方法が共存しています。1 つは MembershipProvider クラスを使用する従来の API で、もう 1 つは ExtendedMembershipProvider クラスを使用する簡易なメンバーシップ API です。2 つの API には互換性がありません。

Visual Studio 2013 と ASP.NET MVC 5 が登場するまで、ASP.NET にはユーザー認証の処理方法が既に多数用意されていました。フォーム認証を使用する場合は、従来のメンバーシップ、Web ページで定義された簡易なメンバーシップ、およびさまざまなカスタム メンバーシップ システムを使用できました。ASP.NET に精通した開発者にとっては、複雑な実際のアプリケーションに独自のメンバーシップ プロバイダーが必要な状況が一般的だったことを考えてみてください。多くの場合、カスタム メンバーシップ システムの構築は、必要なデータベース形式と既存のユーザー資格情報データベースの形式との間に存在している構造上の差を縮めるために行われていました。

もちろん、このような状況はいつまでも続くわけではありません。開発者コミュニティからは、使いやすくて用途が絞り込まれ、各種 ASP.NET と同じ方法で使用できるメンバーシップ用統一システムを求める強い要望が上がりました。この思想は、Visual Studio 2013 で推進された "1 つの ASP.NET" 手法と深く結び付いています。

1 つの ID フレームワーク

認証の目的とは、ID を現在のユーザーに関連付けることです。認証処理では、ID を取得し、指定の資格情報をデータベース内のレコードと比較します。したがって、ASP.NET Identity などの ID システムは、認証マネージャーとストア マネージャーの 2 つの要素で構成されています。ASP.NET Identity フレームワークの場合、認証マネージャーは UserManager<TUser> クラスとして存在しています。このクラスは基本的に、ユーザーがサインインやサインアウトを行うためのファサードを提供します。ストア マネージャーは、UserStore<TUser> クラスのインスタンスです。図 1 に、ASP.NET Identity に基づいた ASP.NET MVC アカウント コントローラー クラスの概要を示します。

図 1 ASP.NET Identity に基づいたコントローラーの基礎

public class AccountController : Controller
{
  public UserManager<ApplicationUser> UserManager { get; private set; }
  public AccountController(UserManager<ApplicationUser> manager)
  {
    UserManager = manager;
  }
  public AccountController() :
    this(new UserManager<ApplicationUser>(
      new UserStore<ApplicationUser>(new ApplicationDbContext())))
  {
  }
  ...
}

このコントローラーは、認証 ID マネージャーである UserManager への参照を保持しています。この UserManager のインスタンスを、コントローラーに挿入します。このコントローラーには、制御の反転 (IoC) フレームワークを使用することも、もっと初歩的な依存関係の注入 (DI) パターンを使用することもできます。DI の場合は 2 つのコントローラーを使用し、どちらかのコントローラーで既定値を受け取ります (図 1 参照)。

一方、ID ストアについては認証 ID マネージャーに挿入して、ここで資格情報の検証に使用します。ID ストアは、UserStore<TUser> クラスとして存在しています。UserStore<TUser> クラスは、次のような複数の型を合成して定義します。

 

public class UserStore<TUser> :
  IUserLoginStore<TUser>,
  IUserClaimStore<TUser>,
  IUserRoleStore<TUser>,
  IUserPasswordStore<TUser>,
  IUserSecurityStampStore<TUser>,
  IUserStore<TUser>,
  IDisposable where TUser : IdentityUser
  {
  }

UserStore<TUser> に実装しているすべてのインターフェイスは、パスワード、ロール、要求、それにもちろんユーザー データなど、ユーザーに関するオプション データを格納する基本リポジトリです。しかし、ID ストアでは実際のデータ ソースを把握する必要があるため、図 1 に示すように、コンストラクター経由でデータ ソースを UserStore クラスに挿入します。

ユーザーのデータに関するストレージは、Entity Framework Code First アプローチを使用して管理しています。つまり、ユーザーの資格情報を格納する物理データベースを作成する必要は、厳密にはありません。代わりに User クラスを定義すれば、基盤となっているフレームワークを利用して、資格情報レコードの格納に最適なデータベースを作成できます。

ApplicationDbContext クラスは、ユーザーのデータを保存する Entity Framework コンテキストをラップします。ApplicationDbContext クラスの定義例を以下に示します。

public class ApplicationDbContext : IdentityDbContext<ApplicationUser>
{
}

基本的に、ASP.NET Identity のデータベース コンテキストは特定のユーザー型の永続性を制御します。ユーザー型は、IUser インターフェイスを実装するか、IdentityUser を継承している必要があります。図 2 に、既定の IdentityUser のソース コードを示します。

図 2 ASP.NET Identity での既定のユーザー クラスの定義

namespace Microsoft.AspNet.Identity.EntityFramework
{
  public class IdentityUser : IUser
  {
    public string Id { get; }
    public string UserName { get; set; }
    public string PasswordHash { get; set; }
    public string SecurityStamp { get; set; }
    public ICollection<IdentityUserRole> Roles { get; private set; }
    public ICollection<IdentityUserClaim> Claims { get; private set; }
    public ICollection<IdentityUserLogin> Logins { get; private set; }
  }
}

以下に、実際のアプリケーションで使用するような、現実的なカスタム ユーザー クラスの例を示します。

public class ApplicationUser : IdentityUser{
  public DateTime Birthdate { get; set; }
}

Entity Framework Code First を使用すると、データベースの構造が重要ではなくなるので、開発方法が大きく変化します。データベースが必要なことに変わりはありませんが、クラスに基づいたコードをデータベースの作成に使用できます。また、Entity Framework Code First の移行ツールを使用すれば、データベースを定義しているクラスを変更することで、以前に作成したデータベースを変更できます (この移行の詳細については、MSDN のデータ アクセス デベロッパー センター (msdn.microsoft.com/ja-jp/data/jj591621.aspx) で「Code First Migrations」を参照してください)。

ユーザーを認証する

ASP.NET Identity は、最新の Open Web Interface for .NET (OWIN) 認証ミドルウェアを基盤としています。このため、一般的な認証手順 (Cookie の作成と確認など) を、ASP.NET/IIS インターフェイスから直接実行する代わりに OWIN の抽象インターフェイス経由で実行できます。OWIN をサポートするには、アカウント コントローラーに次のような便利なプロパティを新しく追加する必要があります。

private IAuthenticationManager AuthenticationManager
{
  get {
  return HttpContext.GetOwinContext().Authentication;
  }
}

IAuthenticationManager インターフェイスは、Microsoft.Owin.Security 名前空間で定義されています。このプロパティは、認証関連の手順にかかわるすべての操作に挿入する必要があるので重要です。以下に、典型的なログイン メソッドを示します。

private async Task SignInAsync(ApplicationUser user, bool isPersistent)
{
  var identity = await UserManager.CreateIdentityAsync(user,
    DefaultAuthenticationTypes.ApplicationCookie);
  AuthenticationManager.SignIn(new AuthenticationProperties() {
    IsPersistent = isPersistent }, identity);
}

SignInAsync メソッドは、認証マネージャーに関連付けられたストアに対して、指定のユーザー名とパスワードをチェックします。ユーザーを登録し、メンバーシップ データベースにユーザーを追加するには、次のようなコードを使用します。

var user = new ApplicationUser() { UserName = model.UserName };
var result = await UserManager.CreateAsync(user, model.Password);
if (result.Succeeded)
{
  await SignInAsync(user, isPersistent: false);
  return RedirectToAction("Index", "Home");
}

つまり、ASP.NET Identity には、認証関連処理の統合 API が用意されています。たとえば、専用データベースに対する認証や、ソーシャル ネットワークの OAuth ベース エンドポイントに対する認証に必要なコードがこのフレームワークにまとまっています。図 3 に、外部のログイン エンジンに対するユーザー認証に必要なコードを示します。図 3 のコードは、(Facebook などに対する) OAuth 認証が正常に完了したときに呼び出します。

図 3 外部エンドポイントを通じた認証プロセスの完了

public async Task<ActionResult> ExternalLoginCallback(
  string loginProvider, string returnUrl)
{
  ClaimsIdentity id = await UserManager
  .Authentication
  .GetExternalIdentityAsync(AuthenticationManager);
  var result = await UserManager
    .Authentication
    .SignInExternalIdentityAsync(
      AuthenticationManager, id);
  if (result.Success)
    return RedirectToLocal(returnUrl);
  else if (User.Identity.IsAuthenticated)
  {
    result = await UserManager
      .Authentication
      .LinkExternalIdentityAsync(
        id, User.Identity.GetUserId());
    if (result.Success)
      return RedirectToLocal(returnUrl);
    else
      return View("ExternalLoginFailure");
  }
}

 

まとめ

私見では、ASP.NET Identity は何年も前に登場している必要があった、機が熟しきったソリューションです。ASP.NET Identity に関する現在の主な問題は、長期間使い続けられる、少なくともさらに新しくて優れたテクノロジが IT 業界に現れるまで使い続けられるほど、汎用性とテスト容易性が高いプログラミング インターフェイスを開発チームが考案しようとしていることです。

近い将来、ASP.NET Identity は昔ながらのメンバーシップが 10 年前に受けていた評価と同じくらい優れたテクノロジになるに違いありません。個人的に気に入っている点は、API の表現力と、さまざまな形式の認証 (組み込み認証、OAuth ベース認証など) をまとめようとする試みです。また、OWIN との統合も長所の 1 つです。これによって、ASP.NET Identity が IIS/ASP.NET などの特定のランタイムからある程度独立しています。

ASP.NET Identity は Visual Studio 2013 に依存していますが、今後のビルドやリリースでは単独のテクノロジとして独立する見込みもあります。今回の記事では、新しい ID API の表面をなぞったにすぎません。今後のビルドやリリースを楽しみにしていてください。

Dino Esposito は、『Architecting Mobile Solutions for the Enterprise』(Microsoft Press、2012 年)、および近日出版予定の『Programming ASP.NET MVC 5』(Microsoft Press) の著者です。JetBrains の .NET および Android プラットフォームのテクニカル エバンジェリストでもあります。世界各国で開催される業界のイベントで頻繁に講演しており、software2cents.wordpress.com (英語) や Twitter (twitter.com/despos、英語) でソフトウェアに関するビジョンを紹介しています。

この記事のレビューに協力してくれた技術スタッフの Pranav Rastogi (マイクロソフト) に心より感謝いたします。