WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

Duende IdentityServer

by WebSurfer 2022年4月7日 12:38

先の記事「VS2022 .NET 6.0 React のユーザー認証」で IdentityServer を勉強しなくてはと書きましたが、そのための第一歩として Duende Software のサイトの Quickstarts を見ながらサンプルを作ってみました。

Duende IdentityServer

上の画像は Visual Studio 2022 のテンプレートで Duende IdentityServer プロジェクトを作成し、それを Visual Studio から実行してブラウザに表示したものです。

以下に、上の画像の Duende IdentityServer プロジェクトの作り方、ASP.NET Core MVC アプリのプロジェクトを作ってその認証を Duende IdentityServer でサポートする方法を備忘録として書いておきます。

(1) テンプレートのインストール

Overview に従って Visual Studio 用のテンプレートをインストールします。コマンドラインから以下のように dotnet コマンド、

dotnet new --install Duende.IdentityServer.Templates

・・・を実行すると、下の画像のように 6 種類のテンプレートが自動的にインストールされます。

テンプレートをインストール

(2) IdentityServer プロジェクトの作成

Visual Studio 2022 を立ち上げて「新しいプロジェクトの作成」画面を開くと以下のように Duende IdentityServer 関係のメニューが表示されます。

Duende IdentityServer

その中の赤枠で囲った ASP.NET Core Identity をユーザー情報のストアに使うテンプレート選択してプロジェクトを作成してみました。プロジェクトを作成する際何故かフレームワークは選択できず自動的に .NET 6.0 になります。

作成したプロジェクトは Quickstarts の中の Using ASP.NET Core Identity に該当します。(記事が少し古いのか、実際に生成されたものと記事の説明が少々異なるのに注意)

中身は ASP.NET Core の Razor Pages アプリで、Visual Studio から実行すると開発サーバーとして Kestrel が立ち上がってアプリが実行され、この記事の一番上の画像のように結果がブラウザに表示されます。

デフォルトではユーザー情報をストアするためのデータベースは SQLite になります。EF Code First の機能を使ってデータベースを生成します。Migration 関係のファイルは Data/Migrations にありますので、パッケージマネージャーコンソールから update-database を実行すればアプリケーションルート直下に AspIdUsers.db というファイルが生成されます。ただしその時点ではユーザーは登録されてないのでログインはできません。

プロジェクトにはユーザーを登録するためのページは含まれてないのでアプリを操作してユーザー登録することはできません。ユーザー情報をシードするために SeedData.cs がプロジェクトに含まれていますが Visual Studio から実行したのではそれは動きません。

dotnet run /seed を実行すれば alice と bob という名前の 2 人のユーザーがパスワード Pass123$ で登録されるようです (未確認です。dotnet コマンドに自信のない自分は Program.cs のコード中の if (args.Contains("/seed")) を true にして実行してユーザー登録しました)。以降は、そのユーザー情報を使ってログインできるようになります。

以上で IdentityServer 側の準備はできました。ただし、後で、認証をサポートする MVC アプリのホスト名などの情報を Config.cs ファイルの設定に反映する必要があることを覚えておいてください。

(3) ASP.NET Core MVC プロジェクトの作成

次に、Duende Software の Interactive Applications with ASP.NET Core の説明に従って ASP.NET Core MVC アプリのプロジェクトを作成し、上で準備した IdentityServer で認証を行うようにします。

Visual Studio 2022 の「ASP.NET Core Web アプリ (Model-View-Controller)」のテンプレートを使って[フレームワーク(F)]を「.NET 6.0 (長期的なサポート)」とし[認証の種類(A)]を「なし」にして MVC アプリのプロジェクトを作成します。

(4) NuGet パッケージのインストール

MVC プロジェクトに Microsoft.AspNetCore.Authentication.OpenIdConnect を NuGet からインストールします。

Microsoft.AspNetCore.Authentication.OpenIdConnect

(5) Program.cs の編集

MVC プロジェクトの Program.cs を開いて以下の「// 追加」とコメントしてあるコードを追加します。options.ClientId と options.ClientSecret は IdentityServer の Config.cs の Client の設定と合わせる必要があるので注意してください。

// 追加
using System.IdentityModel.Tokens.Jwt;

var builder = WebApplication.CreateBuilder(args);

// 追加
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();

// 追加
builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = "Cookies";
    options.DefaultChallengeScheme = "oidc";
})
    .AddCookie("Cookies")
    .AddOpenIdConnect("oidc", options =>
    {
        // IdentityServer の launchSettings.json の設定と合わせる
        // デフォルトで以下の設定になっているはず
        options.Authority = "https://localhost:5001";

        // IdentityServer の Config.cs の Client の設定と合わせる
        options.ClientId = "interactive";
        options.ClientSecret = "49C1A7E1-0C79-4A89-A3D6-A37998FB86B0";

        options.ResponseType = "code";
        options.SaveTokens = true;

        // 以下を追加すると IdentityServer の SeedData.cs で
        // Claims に追加した name, given_name, family_name
        // 情報も User.Claims に含まれるようになる
        options.Scope.Add("profile");
        options.GetClaimsFromUserInfoEndpoint = true;
    });

// ・・・中略・・・

// 追加
app.UseAuthentication();

app.UseAuthorization();

app.MapControllerRoute(
    name: "default",
    pattern: "{controller=Home}/{action=Index}/{id?}");

app.Run();

(6) IdentityServer の Config.cs の修正

IdentityServer プロジェクトの Config.cs を開いて RedirectUris, FrontChannelLogoutUri, PostLogoutRedirectUris のホスト名を MVC プロジェクトのものに変更します。

開発環���では MVC プロジェクトの Properties/launchSettings.json に設定されていますのでそれを見るか、実際にアプリを起動してブラウザのアドレスバーを見てください。IIS Express を使うのか Kestrel を使うのかによって変わるので注意してください。

// interactive client using code flow + pkce
new Client
{
    ClientId = "interactive",
    ClientSecrets = { new Secret("49C1A7E1-0C79-4A89-A3D6-A37998FB86B0".Sha256()) },

    AllowedGrantTypes = GrantTypes.Code,

    // 以下のホスト名の部分を修正
    RedirectUris = { "https://localhost:44351/signin-oidc" },
    FrontChannelLogoutUri = "https://localhost:44351/signout-oidc",
    PostLogoutRedirectUris = { "https://localhost:44351/signout-callback-oidc" },

   AllowOfflineAccess = true,
   AllowedScopes = { "openid", "profile", "scope2" }
},

(7) HomeController の修正

MVC プロジェクトの HomeController を開いて Privacy アクションメソッドに [Authorize] 属性を付与します。これにより匿名ユーザーが Privacy にアクセスしようとすると IdentityServer のログインページにリダイレクトされるようになります。

ログアウトできるように Logout アクションメソッドを追加します。その説明は Adding sign-out に書いてあります。(それを読んでも自分には仕組みは分かりませんでしたが、このアクションメソッドを呼び出せばログアウトできるのは確認しました)

using Microsoft.AspNetCore.Mvc;
using MvcNet6.Models;
using System.Diagnostics;

// 追加
using Microsoft.AspNetCore.Authorization;

namespace MvcNet6.Controllers
{
    public class HomeController : Controller
    {
        // ・・・中略・・・        

        // 追加
        [Authorize]
        public IActionResult Privacy()
        {
            return View();
        }

        // 追加
        public IActionResult Logout()
        {
            return SignOut("Cookies", "oidc");
        }

        // ・・・中略・・・
    }
}

(8) Private.cshtml の書き換え

ステップ (7) で [Auhtorize] 属性を付与したアクションメソッド Privacy のビュー Private.cshtml を以下のように書き換えます。これは参考に Claims 情報と IdentityServer のプロパティ情報を表示するためで、こうしなければならないというものではないです。

@using Microsoft.AspNetCore.Authentication

@{
    ViewData["Title"] = "Privacy Policy";
}
<h2>Claims</h2>

<dl>
    @foreach (var claim in User.Claims)
    {
        <dt>@claim.Type</dt>
        <dd>@claim.Value</dd>
    }
</dl>

<h2>Properties</h2>

<dl>
@{
#nullable disable
    foreach (var prop in (await Context.AuthenticateAsync()).Properties.Items)
    {
        <dt>@prop.Key</dt>
        <dd>@prop.Value</dd>
    }
}
</dl>

(9) _LoginPartial.cshtml の追加

ASP.NET Core MVC プロジェクトを認証に個別のアカウントを選んで作成すると、ログインしたときに画面のメニューバーの右端にユーザー名と Logout リンクが表示されますが、それと同等なものを Views/Shared フォルダに _LoginPartial.cshtml として追加します。

<ul class="navbar-nav">
@{
    string? givenName = User.Claims.FirstOrDefault(u => u.Type == "given_name")?.Value;

    if (givenName != null)
    {
        <li class="nav-item">
            <span class="nav-link">Hello @givenName !</span>
        </li>
        <li class="nav-item">
        <a class="nav-link" asp-action="Logout" asp-controller="Home">Logout</a>
        </li>
    }
    else
    {
        <li><span class="nav-link">ログインしていません</span></li>
    }
}
</ul>

これを <partial name="_LoginPartial" /> というコードで Views/Shared/_Layour.cshtml に追加します。場所は <div class="navbar-collapse ... の div 要素の中の一番下です。

以上で完了です。まず Visual Studio で IdentiryServer プロジェクトを実行した後、MVC プロジェクトを実行します。ブラウザに表示された MVC アプリのメニューバーの Pricacy をクリックすると、以下のように IdentityServer のログイン画面にリダイレクトされます。そこで、IdentiryServer プロジェクトで登録したユーザーの alice または bob でログインできます。

ログイン画面

ログインに成功すると Privacy 画面にリダイレクトされ、下の画像の結果が表示されます。

Privacy 画面

メニューバーの右端の Logout リンクをクリックするとログアウトできます。


さて、ここまでやってみて、それで IdentityServer が十分理解できたのか、先の記事「VS2022 .NET 6.0 React のユーザー認証」で問題となった Authority とか Issuer とはどういうものか分かったのかと聞かれると、正直何も分かってないです。

さらなる勉強が必要なようですが、どうも手に負えそうもないような感じがしています。(汗)


【2022/7/12 追記】

認証サーバー (Duende IdentityServer) のドメインは localhost:5001 で、ASP.NET Core MVC のドメインは localhost:44351 と異なっていますが、それで何故 Cookie ベースの ASP.NET Core MVC のユーザー認証がうまくいくのでしょう?

Fiddler を使って調べてみると、認証プロセスの過程で以下の Fiddler のキャプチャ画像の #40 の通り localhost:44351/signin-odic に要求が出て、応答の Set_Cookie で認証クッキーが返ってきています。

認証クッキーも受領

そのあたりの詳しい仕組みは全く分かっていませんが、とにかく ASP.NET Core MVC のドメイン localhost:44351 での要求で認証クッキーが返ってくるので、認証サーバーのドメイン (localhost:5001) が異なっても問題ないということでした。

Tags: , ,

Authentication

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar