対象: 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 まわりを触る前に必ず読むこと。本書はそこに重複して書かない。
汎用認証サイトの現行版。 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 件。
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 対象。
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で揺れている。
appsettings.json は .gitignore 対象。git に在るのは _appsettings.json(テンプレート)。
_appsettings.json → コピーして appsettings.json を作り、各自の値を埋める
実ファイルには実在の認証情報(管理者アカウント、SMTP、Twilio、Stripe / PAY.JP、
外部ログインの ClientSecret、SaltParameter)が入っている。
報告・コミット メッセージ・Issue 本文に転記しないこと。
設定項目を増やすときは _appsettings.json を直す(実ファイルへの反映は人が行う)。
既定は "UserStoreType": "mem"(プロセス内メモリ)で、DB 無しで起動できる。
DBMS を使うなら sql / ora / npg に変え、root/files/resource/MultiPurposeAuthSite/Sql/
の DDL を流す。Docker で立てる一式は store/(docker-compose.yml)。
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";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。
// 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
と同じ形なので、迷ったらそちらも参照する。
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で指定している。
Config.{MicrosoftAccount,Google,Facebook,Twitter}Authentication が true のときだけ
authenticationBuilder.Add***() する。OAuth2 / OIDC の IdP 側はスクラッチ実装であり、
ここには何も登録されない(#region OAuth2 / OIDC に「スクラッチ実装」とだけ書いてある)。
services.AddDistributedMemoryCache(); // 開発用
//services.AddDistributedSqlServerCache();
//services.AddDistributedRedisCache();複数インスタンスで動かすなら差し替えが要る。 現状は単一プロセス前提。
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.OpenIDConfigsamlmetadata→OAuth2Endpoint.SamlMetadata
設定でパスを変えると、ルーティングが丸ごと変わる。 エンドポイントを足すときは「
Configにプロパティ追加 →_appsettings.jsonにキー追加 →StartupにMapControllerRoute追加」の 3 点セット。../MultiPurposeAuthSite(net48)側のApp_Start/WebApiConfig.cs/RouteConfig.csにも同じ登録が要る。
ロールと管理者ユーザは、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のコメントに明記)。
この認証サイトは、自分自身のクライアントも兼ねている。
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.md5 節「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)で、 各経路の画面を測る
- SP 側(
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.PollForTokenAsync1 つに寄せた) - E2E は承認まで通す経路を測れる(
RT-246.3。CIBA は実機が要るので測れない)
- [Start polling.] →
- 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.md10 節)。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 にして塞ぐ。
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 が持つ。
| 自己テスト(この画面) | 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.md11 節「本番へ切り替えるときに見るもの」) - 認可画面(同意)のように、利用者にも見せる画面へ自己テスト用の表示を足すときは、
同じ設定で隠す(
OAuth2Authorize.cshtmlの「この画面で確かめること」。#246 の項目 3) IsDebug/TestUserPWD/FcmOutboxDirectoryを本番で有効にしない (ProductionCheckが警告する)
同じ機能を両系統で提供するのが原則だが、現状は次の差がある。
| 機能 | 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 では画面は出ない。
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を直接使うのが確実。
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_MultiPurposeAuthSiteCore.batが存在しないRestoreLib1/2.batを呼ぶ(9 節)。 -
log4net3.2.0 に既知の脆弱性(9.1 節)。Open棟梁側は 3.3.0 で、版が割れている。 -
C:\直下配置が前提(2.2 節)。appsettings.jsonは絶対パスを直書きしている。 -
/MultiPurposeAuthSite仮想パス前提(2.5 節)。Cookie の✅LoginPathなどが直書き。LoginPath/LogoutPathの直書きは解消した(#223。4.1 節)。PathBaseは実行時に前置されるので、IIS Express(仮想アプリ)でも Kestrel でも正しく動く。 設定値(OAuth2AuthorizationServerEndpointsRootURIなど)の仮想パス前提は、そのまま残っている。 -
初期データは
/Account/Loginの初回アクセスで作られる(6 節)。 「起動しただけでは管理者が居ない」ことに気付きにくい。 -
IsDebug: trueでテスト ユーザが作られる(6 節)。 -
CORS が
AllowAnyOrigin/AllowAnyMethod/AllowAnyHeader。StartupでUseCorsとAddCors("AllowAllOrigins")の二重定義になっている。 -
Session が
AddDistributedMemoryCache(開発用)のまま(4.3 節)。 -
UTF-8 でないファイルが 2 件ある(Shift_JIS)。
Views/_ViewImports.cshtml(ヘッダ コメントが文字化けする)Views/Manage/ManageTwoFactorAuthenticator.cshtml
他は BOM 付き UTF-8。編集ツールによっては保存時に壊すので注意。
-
ManageController/AccountControllerの WebAuthn 分岐はコメント アウト済み。 View(Add{WebAuthn,MsPass}Data.cshtml)と JS(wwwroot/js/multiauthsite/{ff,ms}Webauthn.js) は残っているが、機能しない(../CommonLibrary/ANALYSIS.md12 節)。 -
AccountController.csは 4402 行、ManageController.csは 3262 行と巨大。 変更は該当#regionに閉じ、全体リファクタは避ける。 -
ErrorControllerはMyBaseMVControllerCoreを継承するが、net48 側は素のController。 両系統でエラー処理の基底が違う。 -
_appsettings.jsonとappsettings.jsonの差分は「秘密情報」だけではない。 テスト用クライアントのredirect_uri_codeも実環境向けに書き換えられている (SPA / Native の redirect_uri)。テンプレートを直すときに取り違えないこと。
-
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 版に同じ変更が要るか判断し、要否を報告する