Skip to content

Latest commit

 

History

History
591 lines (481 loc) · 38.1 KB

File metadata and controls

591 lines (481 loc) · 38.1 KB

ANALYSIS.md — 汎用認証サイト 主要部(MultiPurposeAuthSiteCore / net10.0)コード分析

対象: root/programs/MultiPurposeAuthSiteCore(ASP.NET Core MVC / net10.0) / ブランチ: develop 最終更新: 2026-09-30

本書は コーディング・エージェントが本ディレクトリで作業する際の Context を目的とした分析結果である。

実装の大半は ../CommonLibrary に在る。先に ../CommonLibrary/ANALYSIS.md を読むこと。 本書は「この Web アプリ固有の部分」だけを扱う。 下位互換版(net48)は ../MultiPurposeAuthSite/ANALYSIS.md。

IdP / STS としてのプロトコル適合性、既知の不具合、近代化ロードマップは ../ANALYSIS-IdP.md が一次情報。 OAuth2 / OIDC まわりを触る前に必ず読むこと。本書はそこに重複して書かない。


1. これは何か

汎用認証サイトの現行版。 ASP.NET Core Identity と JWT による OAuth 2.0 / OpenID Connect の IdP(Identity Provider)兼 STS(Security Token Service)。 SAML2 の IdP でもある。

  • ソリューション: MultiPurposeAuthSiteCore.sln → MultiPurposeAuthSiteCore/MultiPurposeAuthSiteCore.csproj (Microsoft.NET.Sdk.Web / net10.0、アセンブリ名 MultiPurposeAuthSite)
  • Startup.cs 方式(Minimal API ではない)。Program.Main → Host.CreateDefaultBuilder。
  • 上流の解説: Open棟梁 Wiki「汎用認証サイト(Multi-purpose Authentication Site)」
  • プロジェクト・ポリシーは リポジトリ ルートの AGENTS.md(CLAUDE.md はそれへのポインタ) に定義済み。 → エージェントは git 操作(add/commit/push/checkout/branch/reset/restore/stash)を行わない。

規模の目安: .cs 10 ファイル / 約 11,600 行、.cshtml 57 件。


2. 前提条件

2.1 Open棟梁のアセンブリを先に用意する(最重要)

OpenTouryo.{Public,Public.Security,Framework,Business} を ../OpenTouryoAssemblies/Build_netcore100/net10.0/*.dll から HintPath で直接参照する。 ProjectReference ではないので、用意しないとビルドできない。

../3_BuildLibsAtOtherRepos.bat                  タグ 03-20 の zip を取得 → ビルド → コピー
../3_BuildLibsAtOtherReposInTimeOfDev.bat       develop の zip で同上
mpas_dev.bat(リポジトリ ルート)               隣に clone 済みの OpenTouryo からビルド出力を xcopy

1_BuildAll.ps1 は、無ければ 2 番目(develop)を自動で呼ぶ(-Libs Force で取り直す)。

OpenTouryoAssemblies/ は .gitignore 対象。

2.2 リポジトリを C:\ 直下に配置する

appsettings.json は C:/root/files/resource/... を直書きしている。

"FxXMLSPDefinition": "C:/root/files/resource/XML/SPDefinition.xml",
"FxLog4NetConfFile": "C:/root/files/resource/Log/SampleLogConf.xml",
"RsaPfxFilePath":    "C:/root/files/resource/X509/SHA256RSA_Server.pfx",
"ContentOfLetterFilePath": "C:/root/files/resource/MultiPurposeAuthSite/Txt"

チェックアウト先が C:\MultiPurposeAuthSite 以外だと、ビルドは通るが実行時に落ちる。 区切りは /(JSON のエスケープ回避)。

注意: FxXMLSPDefinition などが指す C:/root/files/resource/XML/ は Open棟梁側のリソース(OpenTouryo/root/files/resource/Xml/)であり、本リポジトリの root/files/resource/MultiPurposeAuthSite/Xml/ とは別物。ケースも XML / Xml で揺れている。

2.3 設定ファイル

appsettings.json は .gitignore 対象。git に在るのは _appsettings.json(テンプレート)。

_appsettings.json  →  コピーして appsettings.json を作り、各自の値を埋める

実ファイルには実在の認証情報(管理者アカウント、SMTP、Twilio、Stripe / PAY.JP、 外部ログインの ClientSecret、SaltParameter)が入っている。 報告・コミット メッセージ・Issue 本文に転記しないこと。 設定項目を増やすときは _appsettings.json を直す(実ファイルへの反映は人が行う)。

2.4 UserStore

既定は "UserStoreType": "mem"(プロセス内メモリ)で、DB 無しで起動できる。 DBMS を使うなら sql / ora / npg に変え、root/files/resource/MultiPurposeAuthSite/Sql/ の DDL を流す。Docker で立てる一式は store/(docker-compose.yml)。

2.5 起動 URL

Properties/launchSettings.json の既定は https://localhost:44300/MultiPurposeAuthSite。 launchSettings.json はリポジトリ ルートの .gitignore で除外されている (**/Properties/launchSettings.json)。

/MultiPurposeAuthSite という仮想パス配下で動く前提でコードが書かれている点に注意。 Startup.ConfigureServices の Cookie 設定にも直書きがある。

options.LoginPath  = "/MultiPurposeAuthSite/Account/Login";
options.LogoutPath = "/MultiPurposeAuthSite/Account/LogOff";

3. 構成

MultiPurposeAuthSiteCore/
├─ MultiPurposeAuthSiteCore.sln
└─ MultiPurposeAuthSiteCore/
   ├─ Program.cs        … エントリポイント。OAuth2AndOIDCClient.HttpClient を差し込む
   ├─ Startup.cs        … DI 登録・パイプライン・ルーティング(494 行)
   ├─ Controllers/
   │   ├─ AccountController.cs          4594 行  サインイン/アップ、2FA、外部ログイン、
   │   │                                          ID 連携、SAML2/OAuth2 の認可エンドポイント
   │   ├─ ManageController.cs           3263 行  ユーザ属性・2FA・決済情報・GDPR
   │   ├─ HomeController.cs             1440 行  ★テスト用クライアント(Starters)
   │   ├─ OAuth2EndpointController.cs   1343 行  token / userinfo / revoke / introspect /
   │   │                                          jwkcerts / ros / device_authz / ciba_* /
   │   │                                          .well-known / samlmetadata
   │   ├─ OAuth2ResourceServerController.cs 223 行  リソース サーバ側の疎通用 WebAPI
   │   ├─ ErrorController.cs             85 行
   │   ├─ PingController.cs              54 行  死活監視
   │   └─ ValuesController.cs            58 行  疎通確認(`api/values/get`)
   ├─ Views/{Account,Manage,Home,Error,Shared}/*.cshtml
   ├─ wwwroot/{css,js,images,lib}/       … bootstrap / jQuery 等はリポジトリに直接格納
   ├─ _appsettings.json                  … テンプレート(git 管理下)
   ├─ appsettings.json                   … 実ファイル(.gitignore)
   └─ appsettings.Development.json
  • AccountController / ManageController / HomeController / ErrorController は Open棟梁の MyBaseMVControllerCore を継承(Touryo.Infrastructure.Business.Presentation)。
  • OAuth2EndpointController / OAuth2ResourceServerController は素の ControllerBase(WebAPI)。
  • PingController / ValuesController は素の Controller。

4. 起動シーケンス(フレームワークの組み込み方)

// Program.cs
public static void Main(string[] args)
{
    OAuth2AndOIDCClient.HttpClient = new HttpClient();   // ★静的 HttpClient を差し込む
    Program.BuildWebHost(args).Run();
}

// Startup.cs
public Startup(IConfiguration configuration)
{
    Configuration = configuration;
    GetConfigParameter.InitConfiguration(configuration); // ★必須。無いと Config.* が全て null
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    app._UseHttpContextAccessor();   // ★必須。MyHttpContext.Current を有効化する Open棟梁の拡張
    app.UseStaticFiles();
    app.UseCookiePolicy(...);        // HttpOnly=Always / MinimumSameSitePolicy=None
    app.UseSession(...);             // IdleTimeout 30 分、Cookie 名は sessionState:SessionCookieName
    app.UseRouting();
    app.UseAuthentication();
    app.UseAuthorization();
    app.UseCors(...);                // ★認証・認可の後ろ。AllowAnyOrigin/Method/Header
    app.UseEndpoints(...);           // 5 節
}

この 3 点(InitConfiguration / _UseHttpContextAccessor / UseSession)が .NET (Core) で Open棟梁を動かすための定型。 Open棟梁側の Samples4NetCore/Backend/MVC_Sample と同じ形なので、迷ったらそちらも参照する。

4.1 ASP.NET Core Identity の登録

Entity Framework は使わない。 自前ストア(../CommonLibrary)を注入する。

// AddIdentity より前に登録しないと効かない
services.AddScoped<IPasswordHasher<ApplicationUser>, CustomPasswordHasher<ApplicationUser>>();
services.AddScoped<ISecurityStampValidator, SecurityStampValidator<ApplicationUser>>();

services.AddIdentity<ApplicationUser, ApplicationRole>()
    .AddUserStore<UserStoreCore>()
    .AddRoleStore<RoleStoreCore>()
    .AddDefaultTokenProviders();

services.AddTransient<IUserStore<ApplicationUser>, UserStoreCore>();
services.AddTransient<IRoleStore<ApplicationRole>, RoleStoreCore>();
services.AddTransient<IEmailSender, EmailSender>();
services.AddTransient<ISmsSender, SmsSender>();

IdentityOptions(ユーザ名 / パスワード / ロックアウト)はすべて Config.* から取るので、 挙動を変えたいときは appsettings.json を直す。

認証クッキーは ConfigureApplicationCookie で設定する(✅ 修正済み。#223)。

services.ConfigureApplicationCookie(options =>
{
    options.LoginPath = "/Account/Login";    // 接頭辞を直書きしない(PathBase が前置される)
    options.LogoutPath = "/Account/LogOff";
    options.ExpireTimeSpan = Config.AuthCookieExpiresFromHours;
    options.SlidingExpiration = Config.AuthCookieSlidingExpiration;
    options.Cookie.HttpOnly = true;
});

以前は authenticationBuilder.AddCookie(...) に書いていたが、1 つも効いていなかった。 それはスキーム Cookies の設定で、サインインに使うのは Identity.Application (AddIdentity が既定にする)。ブラウザのクッキーも .AspNetCore.Identity.Application だけで、 .AspNetCore.Cookies は発行されていなかった。

ただし Cookies の登録そのものは外せない。 Open棟梁のフレームワーク (MyMVCCoreFilterAttribute)がこのスキームを参照しており、登録が無いと No authentication handler is registered for the scheme 'Cookies' で落ちる (外して E2E を回し、net10.0 側が 91 件失敗して判明)。 登録は残し、設定は書かない(書いても効かないため)。

そのため AuthCookieExpiresFromHours / AuthCookieSlidingExpiration が net10.0 では無視され、 実効値は Identity の既定(14 日 / sliding)だった。雛形の 336 時間がちょうど 14 日なので、 誰も気付かなかった。net48 は App_Start/StartupAuth.cs で正しく適用している。

SecurityStamp の検証は書かない。 Identity が既定で設定しており、 間隔は SecurityStampValidatorOptions.ValidationInterval で指定している。

4.2 外部ログイン

Config.{MicrosoftAccount,Google,Facebook,Twitter}Authentication が true のときだけ authenticationBuilder.Add***() する。OAuth2 / OIDC の IdP 側はスクラッチ実装であり、 ここには何も登録されない(#region OAuth2 / OIDC に「スクラッチ実装」とだけ書いてある)。

4.3 Session は開発用のまま

services.AddDistributedMemoryCache(); // 開発用
//services.AddDistributedSqlServerCache();
//services.AddDistributedRedisCache();

複数インスタンスで動かすなら差し替えが要る。 現状は単一プロセス前提。


5. ルーティング(設定値からエンドポイントを組み立てる)

Startup.Configure の UseEndpoints で、Config.* が返すパスから動的に登録する。 .Substring(1) は先頭の / を落とすため。

ルート名 既定パス(_appsettings.json) 行き先
Saml2Request /saml2request Account.Saml2Request
OAuth2Authorize /authorize Account.OAuth2Authorize
OAuth2Token /token OAuth2Endpoint.OAuth2Token
GetUserClaims /userinfo OAuth2Endpoint.GetUserClaims
RevokeToken /revoke OAuth2Endpoint.RevokeToken
IntrospectToken /introspect OAuth2Endpoint.IntrospectToken
JwksUri /jwkcerts OAuth2Endpoint.JwksUri
RequestObjectUri /ros OAuth2Endpoint.RequestObjectUri
DeviceAuthZAuthorize /device_authz OAuth2Endpoint.DeviceAuthZAuthorize
DeviceAuthZVerify /device_verify Account.DeviceAuthZVerify
CibaAuthorize /ciba_authz OAuth2Endpoint.CibaAuthorize
CibaPushResult /ciba_result OAuth2Endpoint.CibaPushResult
SetDeviceToken /SetDeviceToken OAuth2Endpoint.SetDeviceToken
TwoFactorPushResult /2fa_result OAuth2Endpoint.TwoFactorPushResult(#213)
TestHybridFlow /TestHybridFlowWebAPI OAuth2ResourceServer.TestHybridFlow
ChageToUser /ChageToUser OAuth2ResourceServer.ChageToUser
default {controller=Home}/{action=Index}/{id?} —

固定パスは [Route] 属性で 2 つだけ。

  • .well-known/openid-configuration → OAuth2Endpoint.OpenIDConfig
  • samlmetadata → OAuth2Endpoint.SamlMetadata

設定でパスを変えると、ルーティングが丸ごと変わる。 エンドポイントを足すときは「Config にプロパティ追加 → _appsettings.json にキー追加 → Startup に MapControllerRoute 追加」の 3 点セット。 ../MultiPurposeAuthSite(net48)側の App_Start/WebApiConfig.cs / RouteConfig.cs にも同じ登録が要る。


6. 初期データの生成(気付きにくい)

ロールと管理者ユーザは、GET /Account/Login と GET /Account/Register の初回アクセスで 遅延生成される。 起動時ではない。

AccountController.Login/Register  →  CreateData()   (SemaphoreSlim で 1 本に絞る)
   ├ STS 専用モードなら何もしない
   ├ Memory Provider …… static フラグ HasCreated で 1 回だけ
   ├ DBMS Provider  …… DataAccess.IsDBMSInitialized()([Roles] の件数)で判定
   ├ ロール作成: SystemAdmin / Admin / User
   ├ 管理者作成: Config.AdministratorUID / AdministratorPWD → 3 ロール全付与
   └ Config.IsDebug かつ TestUserPWD が非空なら
        super_tanaka@gmail.com(User+Admin) / tanaka@gmail.com(User)を作成
  • IsDebug を true のまま公開すると、テスト ユーザが作られる。
  • AdministratorPWD は初期化後に設定から消してよい(_appsettings.json のコメントに明記)。

7. テスト用クライアント(HomeController / Views/Home)

この認証サイトは、自分自身のクライアントも兼ねている。 HomeController はほぼ全部が「各フローを画面から叩くためのテスト用クライアント」。

この節は net48 版にも当てはまる(自己テストは両アプリに同文で置いている)。 net48 側にしか無い話(新しい View を csproj の <Content Include> に足す など)は ../MultiPurposeAuthSite/ANALYSIS.md の 3 節にある。

クライアント側の組み立ては CommonLibrary/Extensions/Sts/SelfTestClient.cs に寄せてある(#246)。

受け持ち
SelfTestClient 鍵を読み、JWT を作る(Request Object・CIBA の要求・client_assertion)。/ros と /par は「組み立て → 預ける → 応答を解く」までを 1 つにしている。CIBA と Device AuthZ はポーリングの通し(#246 の 3-b)、SAML2 は応答(アサーション)の検証(同 項目 3)
Helper WebAPI 呼び出し + コンテナ化の URL 変換(全メソッドが GetContainerizatedAuthZServerUri を通る)
HomeController どのパターンを試すかだけを決める

寄せた理由は、両アプリ(net48 / net10.0)に同文で二重に在ったこと。 自己テストのパターンを増やすたびに二重が増える状態だった。

ID フェデレーションは /userinfo と /token のどちらも Helper を通さない(AccountController)。 /token は #140 の段階 3 まで通っていた(=宛先が書き換わっていた)。

この経路は、#140 の段階 3 で直したが、まだ動かしていない。 E2E で駆動できず(上流の IdP が要る)、目視は上流をコンテナで建ててから行う予定(別 Issue)。 詳細は TESTING.md 5 節「ID フェデレーション」。 Helper はホストをコンテナの認可サーバへ書き換えるので、他の IdP を叩く呼び出しは壊れる。 呼び出し側にその理由を書いてある。

  • Saml2OAuth2Starters.cshtml … SAML2 / Authorization Code / Implicit / Hybrid / PKCE / FAPI1 / FAPI2 / その他を、クライアントと response_mode を選んで開始する画面
  • SAML2 のアサーションを画面に出す(#246 の項目 3。それまでは「最も手薄」だった)
    • SP 側(AccountController.AssertionConsumerService)は、応答を検証した結果を ?ret=認証完了(nameId=…) / ?ret=認証失敗 という URL に載せるだけで、 どこで落ちたのかが分からず、読み取った属性も XML も捨てていた (「必要に応じて samlResponse2 を読んで拡張可能」というコメントだけが在った)
    • いまは Views/Account/Saml2Response.cshtml に、判定(NORMAL_END / ABNORMAL_END)と理由、 署名の検証と Issuer の一致を別々に、NameID / NameIDFormat / Audience / StatusCode / AuthnContextClassRef / InResponseTo / Recipient / NotOnOrAfter / RelayState(送った state との照合)、そしてアサーションの XML(字下げのみ整形)を出す
    • 署名の付き方はバインディングで違う。 Redirect(GET)はクエリ文字列に付き(SigAlg)、 POST は XML の中に付く(SignatureValue)。画面はどちらで受けたかを出す
    • E2E は RT-246.4(Redirect)/ RT-246.5(POST)/ RT-246.7(要求は POST・応答は Redirect)で、 各経路の画面を測る
  • DeviceAuthZResponse.cshtml … Device Authorization Grant の user_code 表示(QR は qrcode.js)。 interval と expires_in も出し、interval を hidden で次の POST へ持ち回す(#246 の 3-b)
  • Device Authorization Grant のポーリングも、判定と理由を画面に出す(#246 の 3-a。CIBA と同じ)
    • [Start polling.] → DeviceAuthZPollingResult.cshtml に、判定(NORMAL_END / ABNORMAL_END)と理由、 ポーリングの間隔 × 回数 / 上限、トークンと /userinfo の応答を出す
    • 間隔は /device_authz が返した interval に従う(RFC 8628 §3.5)。 以前は ExponentialBackoff(10, 5) で、サーバが返した値と無関係だった
    • ポーリングの実体は CIBA と同じ(SelfTestClient.PollForTokenAsync 1 つに寄せた)
    • E2E は承認まで通す経路を測れる(RT-246.3。CIBA は実機が要るので測れない)
  • PAR(RFC 9126)の口も自己テストにある(#246)
    • Saml2OAuth2Starters.cshtml の submit.AuthorizationCodeFAPI2_PAR … Open棟梁 のクライアント実装 (OAuth2AndOIDCClient.PushAuthorizationRequestAsync)で /par に預け、 request_uri と expires_in を画面(PushedAuthorizationResponse.cshtml)で見せてから認可へ進む
    • 既存の FAPI2 のボタンは /ros(独自。RFC 9101 §5.2.1 の任意機能)。両方を押し比べられる
    • E2E は実装側のライブラリを使わないので、Open棟梁 クライアントとの相互接続性はここでしか見ていない(RT-246.1)
  • CIBA(FAPI-CIBA Profile)は、判定と理由を画面に出す(#246 の 3-a / 3-b)
    • Saml2OAuth2Starters.cshtml の submit.FAPI_CIBA_Profile … 認証要求(ES256)→ /ciba_authz → /token のポーリング → /userinfo までを通し、判定(NORMAL_END / ABNORMAL_END)とその理由を CibaProfileResponse.cshtml に出す(auth_req_id・interval・ポーリング回数・各応答も)
    • 以前は ?ret=OK_ + 判定 という URL に移るだけで、OK_ が接頭辞だと読めず、 ?ret=OK_ABNORMAL_END の可否が分からなかった(失敗した理由も出ていなかった)
    • ポーリングは /ciba_authz が返す interval(既定 5 秒)に従い、60 秒で打ち切る。 以前は 30 ミリ秒間隔で上限が無く、承認されなければ要求の期限(600 秒)まで /token を叩き続けていた (画面のタブを閉じても止まらなかった)
    • 承認まで通す経路(NORMAL_END)は、実機の認証デバイスが要る (authentication_device/CHEATSHEET.md 10 節)。E2E は、端末を登録していない利用者で ABNORMAL_END と理由が画面に出ることを測る(RT-246.2)
  • FAPI2 のトークン交換は private_key_jwt(#246)。 以前は client_secret を空で送り、クライアント証明書(TB)が付くことを前提にしていたが、 ClientCertPfxFilePath が設定されていない配置では /token が 401 になり、結果画面まで通らなかった (/ros の既存ボタンも同じ)。FAPI 2.0 は MTLS と private_key_jwt の両方を認めるので、 証明書の配置を前提にしない方に寄せた。mTLS の経路は E2E(FA-6)が測る
  • ログアウト(RP-Initiated Logout。#232)の口も 2 つある
    • Saml2OAuth2Starters.cshtml の submit.EndSession … id_token_hint 無しで /end_session へ。 確認画面の経路を試す
    • OAuth2AuthorizationCodeGrantClient.cshtml の Sign out … 取得した id_token を id_token_hint に載せて /end_session へ POST。確認なしで戻る経路を試す (TestClient に post_logout_redirect_uri の登録が要る。雛形は test_self_logout)。 openid が無いフロー(id_token が発行されない)では、戻り先を送らず確認画面の経路になる (画面にその理由を出す)
  • OAuth2ClientAuthenticationFlow.cshtml / PostBinding.cshtml / Scroll.cshtml
  • 対応する Redirect 先は Account / Manage 側 (OAuth2AuthorizationCodeGrantClient / OAuth2ImplicitGrantClient)

_appsettings.json の OAuth2ClientsInformation には test_self_code / test_self_token という予約 redirect_uri を持つテスト用クライアントが 定義されている。本番では IsLockedDownTestEndpoints を true にして塞ぐ。

パターンとボタンの対応(#246 の項目 2)

Discovery が広告しているもの(grant_types_supported / response_types_supported / response_modes_supported / token_endpoint_auth_methods_supported)と、SAML2・拡張仕様を並べ、 1 つずつボタンを当てた一覧である。「無い」ものは理由と、誰が測るかを書く。

パターン 自己テストのボタン(Saml2OAuth2Starters / 結果画面) 備考
authorization_code Test Authorization Code Flow / (OIDC)
implicit(token / id_token / id_token token) Test Implicit Flow ×3 雛形の既定で無効(#220)。無効なときは押せない表示にしている
Hybrid(code id_token / code token / code id_token token) Test Hybrid Flow ×3
PKCE(plain / S256) PKCE plain / S256(+ SPA 用の 2 つ)
password(ROPC) Test ResourceOwner Password Credentials Flow 雛形の既定で無効(#220)
client_credentials Test Client Credentials Flow
refresh_token 結果画面の [Refresh]
urn:ietf:params:oauth:grant-type:jwt-bearer Test JWT Bearer Token Flow
CIBA(…:openid:params:grant-type:ciba) Test FAPI CIBA Profile (FAPI2) 認証デバイスの登録と承認が要る(判定と理由は画面に出る。#246 の 3-a)
Device(…:oauth:grant-type:device_code) Test Device Authorization Grant → [Start polling.] 承認は /device_verify(画面のリンク)
/revoke / /introspect 結果画面の [RevokeAccess] [IntrospectAccess] [RevokeRefresh] [IntrospectRefresh]
/userinfo 結果画面の [Get user claims]/OAuth2ClientAuthenticationFlow.cshtml
FAPI1(CC / CC+OIDC / PC+PKCE) Test Authorization Code Flow (FAPI1 …) ×3 PC+PKCE は、S256 の値を plain と宣言していたため必ず失敗していた(#245 の段階 2 で修正。RT-245.6)
FAPI2(/ros 経由) Test Authorization Code Flow (FAPI2 CC) /ros は独自(RFC 9101 §5.2.1 の任意機能)
PAR(RFC 9126) Test Authorization Code Flow (FAPI2 CC, PAR) #246 で追加。 Open棟梁 のクライアントで /par に預ける
SAML2 の 4 バインディング Saml2 Redirect Redirect / Redirect Post / Post Post / Post Redirect 4 つ目は #246 で追加(要求は POST、応答は Redirect)
SAML2 のメタデータ 画面下の samlmetadata リンク
RP-Initiated Logout Test RP-Initiated Logout(確認画面の経路)/結果画面の [Sign out](id_token_hint の経路) #232
response_mode(query / fragment / form_post + JARM の 3 種) ドロップダウン
prompt / max_age ドロップダウン #246 で追加。 prompt=none 以外は未対応(C-3)。max_age の超過は #247 で対応(再認証 / login_required / invalid_request)
client_secret_basic 通常の認可コードの交換(画面に方式が出る) #246
client_secret_post PKCE の交換(Open棟梁 クライアントの既定がこちら) 方式としては選べない。 フローに紐付く。E2E が測る(RT-238 / RT-239)
private_key_jwt FAPI1 / FAPI2 / PAR の交換 同上(RT-238)
tls_client_auth(mTLS) 無い ブラウザからは試せない(クライアント証明書の提示が要る)。E2E の FA-6 が測る
Hybrid-IdP(ID フェデレーション) 無い 外部 IdP の登録が要る。 サインイン画面の外部ログインから入る
WebAuthn / MS Passport net48 版に WebAuthnStarters.cshtml が残っているが動かない ライブラリごと無効(../CommonLibrary/ANALYSIS.md 12 節)
2FA のプッシュ承認 ボタンではなくサインインの経路(MobileApp を選ぶ) 認証デバイスが要る(#213 / #216)

方針。 「選べない」ものを無理にボタンにしない。 クライアント認証の方式のようにフローに紐付いているものは、 画面には「何を送ったか」を出し(#246 の項目 3)、組み合わせの網羅は E2E に任せる (次の「自己テストと E2E の役割」のとおり)。

自己テストと E2E の役割(#246 の項目 4)

自己テストは「人でなければ確かめられないもの」を置く場であり、合否の判定は E2E が持つ。

自己テスト(この画面) E2E(Tests/E2ETests)
何のためにあるか 人が目で確かめる(実値・画面・遷移) 合否を自動で判定する(回帰と異常系)
得意なもの 認証デバイスとプッシュ通知/ブラウザの遷移と同意画面/アサーションと JWT の実値の目視/Open棟梁 のクライアント ライブラリとの相互接続 パラメタの異常系、境界値、エラー コード、応答の形(JSON の型・HTTP ステータス)、両系統の差
使うクライアント実装 Open棟梁 のクライアント(OAuth2AndOIDCClient など) 使わない。 自前に再実装してブラックボックスに保つ
判定 人の目(画面に判定と理由を出す) TestReport の検証(OK / NG)
秘密情報 画面に出す(自己テストの目的そのもの) 出さない(TESTING.md 9 節)

この線引きから出てくる決まり。

  • E2E から自己テストを駆動してよい(IdPClient.StartSelfTestAsync)。 Open棟梁 のクライアントを通る経路はそこしか無いので、 相互接続性の回帰だけは E2E が押さえる(RT-197 / RT-246)
  • 自己テストに「合否の自動判定」を足さない。 判定を増やすなら E2E に足す
  • 人手が要る経路は、E2E では Skip にして理由を書く(例 : CIBA の承認は実機が要る。RT-246.2)
  • 画面(Razor)は実行時コンパイルなので、自己テストの画面を足したら E2E で一度は開く (ビルドでは誤りが出ない)

本番に出してよい範囲。

  • 自己テストの画面と口は、IsLockedDownTestEndpoints を true にすれば塞がる (/Home/Saml2OAuth2Starters、テスト用のリダイレクト先、/TestHybridFlow、 api/Values。詳細は CONFIGURATION.md 11 節「本番へ切り替えるときに見るもの」)
  • 認可画面(同意)のように、利用者にも見せる画面へ自己テスト用の表示を足すときは、 同じ設定で隠す(OAuth2Authorize.cshtml の「この画面で確かめること」。#246 の項目 3)
  • IsDebug / TestUserPWD / FcmOutboxDirectory を本番で有効にしない (ProductionCheck が警告する)

8. net48 版との機能差(重要)

同じ機能を両系統で提供するのが原則だが、現状は次の差がある。

機能 net10.0(本ディレクトリ) net48(../MultiPurposeAuthSite)
TOTP(Authenticator アプリ 2FA) ✓ あり(EnableTwoFactorAuthenticator / リカバリ コード / ManageTwoFactorAuthenticator) ✗ 無し
プッシュ 2FA(MobileApp) ✓ あり(SendCode の中で一覧に足し、コードは Email で作る。#213) ✓ あり(2FAプロバイダとして登録する。Manager/MobileAppTokenProvider。#216)
ユーザ・ロール管理画面 ✗ 無し(Config.EnableAdministrationOfUsersAndRoles を読む Controller が無い) ✓ UsersAdminController / RolesAdminController
FIDO2 サーバ用 WebAPI ✗ 無し △ Fido2ServerController.cs は在るがビルド対象外
疎通用 WebAPI ✓ ValuesController ✗
WebAuthn / MS Passport ✗(../CommonLibrary 側ごと無効) ✗(同左)

EnableAdministrationOfUsersAndRoles は .NET 側では「STS 専用モードの判定」にしか 効いていない。 管理画面そのものが無いため、true にしても net10.0 では画面は出ない。


9. ビルドと実行

dotnet build MultiPurposeAuthSiteCore.sln
dotnet run --project MultiPurposeAuthSiteCore/MultiPurposeAuthSiteCore.csproj

../10_MultiPurposeAuthSiteCore.bat は ../z_Common.bat を読んでから dotnet restore → dotnet msbuild する(CommandLineToolsCore.sln も併せてビルドする)。

10_MultiPurposeAuthSiteCore.bat は現状そのままでは通らない。 RestoreLib1.bat(npm i)と RestoreLib2.bat を call しているが、 この 2 ファイルはコミット Reboot:2(0c9a757)で削除済み。 node_modules を消す行も含めて、npm による静的ファイル取得は廃止され、 wwwroot/lib/(bootstrap / jquery / jquery-validation / jquery-validation-unobtrusive)は リポジトリに直接格納する方式に変わっている。 エージェントは bat を経由せず dotnet build を直接使うのが確実。

9.1 現状のビルド結果(実測 2026-09-27)

1_BuildAll.ps1 の net10.0 ステップ … 0 エラー / 0 警告(#242)。

落とした 2 つ。再発したら同じ手を使う。

警告 もとの行数 対処
MSB3277 33 Microsoft.Data.SqlClient を 7.0.2 にした。Open棟梁 の Public / Public.Security が 7.0.0.0 を参照しており、6.1.4(6.0.0.0)との競合だった。Open棟梁 のアセンブリを差し替えたら、この版も見直す
NU1901 6 Microsoft.VisualStudio.Web.CodeGeneration.Design の参照を外した(スキャフォールディング専用の開発時ツールで、ビルド・実行には不要)。再び入れると、推移的依存で戻ってくる

log4net は 3.2.0 → 3.3.0 に上げた(Dependabot PR #181 と同じ内容)。 上げるまでは、ビルドは通るのに実行時に落ちていた。

FileNotFoundException: Could not load file or assembly
'log4net, Version=3.3.0.0, Culture=neutral, PublicKeyToken=669e0ddf0bb1aa2a'

OpenTouryo.Public(net48 / net10.0 とも)が log4net 3.3.0.0 を参照するのに対し、 本プロジェクトが 3.2.0 を固定していたため、bin には 3.2.0 が配置される。 .NET (Core) には binding redirect が無いので、そのまま FileNotFoundException になる。 MSB3277 の版競合警告は、この実行時エラーの予兆だった。

版を上げた結果、警告 9 → 6、NU1902(log4net の既知脆弱性)は解消。

net48 側は packages.config / csproj に log4net を持たず、 OpenTouryoAssemblies/Build_net48/log4net.dll を使うため、この問題は起きない。


10. 落とし穴 / 既知の不整合

  1. 10_MultiPurposeAuthSiteCore.bat が存在しない RestoreLib1/2.bat を呼ぶ(9 節)。

  2. log4net 3.2.0 に既知の脆弱性(9.1 節)。Open棟梁側は 3.3.0 で、版が割れている。

  3. C:\ 直下配置が前提(2.2 節)。appsettings.json は絶対パスを直書きしている。

  4. /MultiPurposeAuthSite 仮想パス前提(2.5 節)。 Cookie の LoginPath などが直書き。 ✅ LoginPath / LogoutPath の直書きは解消した(#223。4.1 節)。 PathBase は実行時に前置されるので、IIS Express(仮想アプリ)でも Kestrel でも正しく動く。 設定値(OAuth2AuthorizationServerEndpointsRootURI など)の仮想パス前提は、そのまま残っている。

  5. 初期データは /Account/Login の初回アクセスで作られる(6 節)。 「起動しただけでは管理者が居ない」ことに気付きにくい。

  6. IsDebug: true でテスト ユーザが作られる(6 節)。

  7. CORS が AllowAnyOrigin / AllowAnyMethod / AllowAnyHeader。 Startup で UseCors と AddCors("AllowAllOrigins") の二重定義になっている。

  8. Session が AddDistributedMemoryCache(開発用)のまま(4.3 節)。

  9. UTF-8 でないファイルが 2 件ある(Shift_JIS)。

    • Views/_ViewImports.cshtml(ヘッダ コメントが文字化けする)
    • Views/Manage/ManageTwoFactorAuthenticator.cshtml

    他は BOM 付き UTF-8。編集ツールによっては保存時に壊すので注意。

  10. ManageController / AccountController の WebAuthn 分岐はコメント アウト済み。 View(Add{WebAuthn,MsPass}Data.cshtml)と JS(wwwroot/js/multiauthsite/{ff,ms}Webauthn.js) は残っているが、機能しない(../CommonLibrary/ANALYSIS.md 12 節)。

  11. AccountController.cs は 4402 行、ManageController.cs は 3262 行と巨大。 変更は該当 #region に閉じ、全体リファクタは避ける。

  12. ErrorController は MyBaseMVControllerCore を継承するが、net48 側は素の Controller。 両系統でエラー処理の基底が違う。

  13. _appsettings.json と appsettings.json の差分は「秘密情報」だけではない。 テスト用クライアントの redirect_uri_code も実環境向けに書き換えられている (SPA / Native の redirect_uri)。テンプレートを直すときに取り違えないこと。


11. エージェント向け作業チェックリスト

  • AGENTS.md のポリシー遵守(git 操作をしない)
  • 変更が ../CommonLibrary 側の話でないか確認する (ストア・トークン・設定・通知・リソースは全て向こう)
  • エンドポイントを足す → Config + _appsettings.json + Startup.UseEndpoints + net48 側の WebApiConfig / RouteConfig の 4 点
  • 画面を足す → View + ViewModel(../CommonLibrary/ViewModels)+ リソース(../CommonLibrary/Resources)
  • 文言は直書きせずリソースへ
  • appsettings.json(実ファイル)を直したら、同じ変更を _appsettings.json にも入れる。 秘密情報は転記しない
  • 新規 .cs / .cshtml は BOM 付き UTF-8で保存する
  • 新規 .cs にはヘッダ コメント(Apache License + クラス名・日本語名・更新履歴)を付与。 既存 .cs の変更時は更新履歴に 1 行追記
  • ビルド確認: dotnet build MultiPurposeAuthSiteCore.sln (警告本数が増えていないかも見る)
  • net48 版に同じ変更が要るか判断し、要否を報告する