認証方式の内部動作(Provider・フォールバック・2 段階認証・パスキー)
プリザンターのログイン方式は App_Data/Parameters/Authentication.json の Provider で 1 つだけ選びます。このページでは、方式ごとにログイン処理がどこを通るか、方式どうしのフォールバックの有無、2 段階認証・パスキー・TrustedProxy 認証がどの段階で効くかを、1.5.8.1 のソースに沿ってまとめます。
Providerは 1 つしか設定できません。ローカル DB へフォールバックするのは"LDAP+Local"だけです(SAML はAllowOriginalLoginの条件付きでローカルログインを残します)。- 2 段階認証はログインフォームを通ったときだけ動きます。SAML の SSO、IIS の Windows SSO、TrustedProxy 認証、パスキーでのログインでは求められません。
- パスキーは
Providerと関係なく、PasskeyParameters.Enabledがtrueなら使えます。
Cookie・API キー・Bearer ヘッダーなど、ログインした後にリクエストごとにユーザーを特定する仕組みは 認証基盤(Cookie・API キー・Bearer) を見てください。
Provider の一覧
Provider | ログインフォームでの認証先 | フォールバック | 認証成功時のユーザー同期 |
|---|---|---|---|
null(既定) | ローカル DB | なし | なし |
"LDAP" | LDAP | なし | あり |
"Windows" | LDAP(AD) | なし | あり |
"LDAP+Local" | LDAP → 失敗したらローカル DB | ローカル DB | LDAP 成功時のみ |
"SAML" | ローカル DB(AllowOriginalLogin で制限) | —(SSO は別の流れ) | SSO 時にあり |
"SAML-MultiTenant" | 同上 | 同上 | 同上 |
"Extension" | 拡張認証 | — | — |
Authentication.json のパラメータは Authentication.cs で定義されています。Provider・DsProvider・ServiceId・ExtensionUrl・RejectUnregisteredUser・PasskeyParameters・LdapParameters・SamlParameters・TrustedProxyParameters の 9 項目です。
"Extension" は Extension.Authenticate を呼びますが、中身は NotImplementedException を投げるだけです。本体のままでは使えません。
ログインフォームの処理の流れ
ログインフォームの送信は UsersController.Authenticate → Authentications.SignIn → UserModel.Authenticate(returnUrl 版) の順に進みます。チェックの順番は次のとおりです。
図を読み込み中…
RejectUnregisteredUserのチェックは、ログイン ID が一致する有効(Disabled = false)なユーザーがちょうど 1 件ではないときに拒否します(RejectUnregisteredUser)。- ログイン有効期限(
LoginExpired)は、ユーザーの「ログイン有効期限」を過ぎたとき、または「ログイン有効期間(日)」が設定されていて最終ログインからその日数を過ぎたときに該当します(LoginExpired)。
Provider ごとの分岐
Provider ごとの認証は UserModel.Authenticate(Context) の switch です。
図を読み込み中…
どの分岐でも、最後に UpdateLockout でロックアウトカウンタを更新します。
ローカル DB 認証(GetByCredentials)
GetByCredentials は、ログイン ID とハッシュ化したパスワード、Disabled = false を条件に Users テーブルを検索し、1 件取れれば成功です。テナント選択(SelectedTenantId)が送られていればテナント ID も条件に加えます。
ロックアウト
UpdateLockout は Security.json の LockoutCount が 1 以上のときだけ動きます(既定は 0 で無効)。
- 認証成功: ロックアウト中でなければ
LockoutCounterを 0 に戻す - 認証失敗:
LockoutCounterを 1 増やし、LockoutCountに達したらLockout = trueにする
ロックアウトの判定は Provider 別の認証の後にあるため、ロックアウト中のユーザーが正しいパスワードを入れると UserLockout、誤ったパスワードなら Deny が返ります。
LDAP 認証
実装の切り替え
Ldap.cs が DsProvider と OS で実装を選びます。
| 条件 | 実装クラス | ライブラリ |
|---|---|---|
DsProvider が "Novell" | LdapNovellDs | Novell.Directory.Ldap |
DsProvider 未指定で OS が Windows | LdapDs | System.DirectoryServices |
| それ以外(Linux など) | LdapNovellDs | Novell.Directory.Ldap |
認証の手順の違い
LdapDs(Windows) | LdapNovellDs | |
|---|---|---|
| 接続 | 入力されたログイン ID(LdapLoginPattern があれば {loginId} を置換した値)とパスワードで接続 | LdapSyncUser / LdapSyncPassword で接続してユーザーを検索し、見つかった DN と入力パスワードで Bind |
| 検索フィルタ | LdapSearchPattern があればそれ({loginId} を置換)、なければ (LdapSearchProperty=ログインID) | (LdapSearchProperty=ログインID) |
| 成功時 | UpdateOrInsert でユーザー情報を同期 | 同左 |
LdapNovellDs の認証では LdapSearchPattern と LdapLoginPattern は使われません(LdapNovellDs.Authenticate)。
複数の LDAP 設定を順に試す
LdapParameters は配列で、先頭から順に試します。ただし次の設定へ進む条件は実装で違います。
LdapDs: 検索時の例外が「data 52e」(資格情報が無効)なら次の設定へ進み、それ以外の例外はログを出して失敗で終わります。検索結果が無いときも次の設定へ進みます(LdapDs.Authenticate)。LdapNovellDs:LdapExceptionなら(「data 52e」以外はログを出したうえで)次の設定へ進み、それ以外の例外は失敗で終わります。
ユーザー同期
認証に成功すると UpdateOrInsert が呼ばれ、LDAP の属性(LdapDeptCode・LdapDeptName・LdapUserCode・LdapFirstName + LdapLastName・LdapMailAddress・LdapExtendedAttributes など、Ldap.cs のパラメータで指定)が Users・Depts・MailAddresses に反映されます。
Windows 認証
Provider = "Windows" には 2 つの経路があります。
- IIS の Windows 認証(SSO): IIS で Windows 認証が有効だと、ブラウザと IIS の間で Negotiate / NTLM の認証が行われ、プリザンターには Windows のユーザー名が渡ります。セッション開始時のミドルウェアが
WindowsAuthenticatedで検出し、Ldap.UpdateOrInsertでユーザーを同期してセッションを作ります(Startup.cs)。RejectUnregisteredUserがtrueのときは、既に登録済みのユーザーだけが対象です。 - ログインフォーム:
case "Windows":はcase "LDAP":と同じ分岐で、Ldap.Authenticateだけを呼びます。
Windows 認証かどうかは Context.AuthenticationsWindows で判定します。Provider が "Windows" のとき、または HttpContext.User.Identity の型名に Windows を含むときに true です。
ローカル DB へはフォールバックしない
IIS で匿名認証も有効にすると、Windows 認証に失敗したユーザーもログインフォームに到達できます。しかしフォームから送った資格情報は Ldap.Authenticate(AD)でだけ認証され、ローカル DB では認証されません。AD に居ないローカル専用のユーザー(管理者など)は、Provider = "Windows" のままではログインする手段がありません。SAML の AllowOriginalLogin にあたる仕組みも Windows 認証にはありません。
| IIS の設定 | Windows 認証が通るとき | Windows 認証が通らないとき |
|---|---|---|
| Windows 認証のみ | SSO でログイン | 401 でアクセス不可 |
| Windows 認証 + 匿名認証 | SSO でログイン | ログインフォーム → AD でのみ認証 |
"Windows" | "LDAP+Local" | |
|---|---|---|
| ログイン画面 | 出ない(IIS が透過的に認証) | 常にフォームで入力 |
| AD の認証 | IIS の Negotiate / NTLM | Ldap.Authenticate(Bind) |
| ローカル DB へのフォールバック | なし | あり |
| IIS の Windows 認証モジュール | 必要 | 不要 |
SSO とローカル DB へのフォールバックを両立する Provider はありません。SSO が必要なら "Windows"、ローカル DB へのフォールバックが必要なら "LDAP+Local"(LDAP の接続先を AD にする)を選びます。両方を実現する改修案は Windows 認証とローカル認証の併用(Windows+Local) にまとめています。
SAML 認証
SAML の設定手順と属性マッピングは 認証(Google Workspace の SAML SSO / SMTP の OAuth) と Microsoft Entra ID で SAML SSO にあります。ここでは内部の分岐だけを補足します。
- SSO の応答は
Saml.SamlLoginで処理され、ユーザー同期のあとAllowAfterUrlで直接 Cookie を発行します。ログインフォームの流れ(ログイン有効期限・2 段階認証・パスワード有効期限のチェック)は通りません。無効ユーザー・ロックアウト中のユーザーは拒否されます。 - テナントの決め方は Provider で違います。
"SAML" | "SAML-MultiTenant" | |
|---|---|---|
| テナント | SamlParameters.SamlTenantId の固定値 | IdP の Issuer の最後のパス要素(SSO コード)と Tenants.Comments が一致するテナント |
| テナントが無いとき | SamlTenantId で DefaultTenant を作成 | 作成しない |
| ユーザー名(Name 属性)が空 | ログイン ID を名前にする | エラー(EmptyUserName) |
- ログインフォームからのローカルログインは
GetByCredentialsで認証し、テナントの契約設定AllowOriginalLoginが0で、かつテナント管理者でなければ失敗にします。
2 段階認証
2 段階認証(Secondary Authentication)は Security.json の SecondaryAuthentication で設定します(定義は SecondaryAuthentication.cs)。
| 項目 | 既定値 | 内容 |
|---|---|---|
Mode | "None" | None(無効)/ DefaultEnable(既定で有効、ユーザーごとに無効化可)/ DefaultDisable(既定で無効、ユーザーごとに有効化可) |
NotificationType | "Mail" | Mail(メールで認証コード)/ Totp(認証アプリ) |
CountTolerances | 1 | TOTP で何ステップ前(30 秒単位)まで遡って照合するか |
NotificationMailBcc | false | 認証コードのメールを BCC でも送るか |
AuthenticationCodeCharacterType | "Number" | メールのコードの文字種。Number / Letter / それ以外は数字と英字 |
AuthenticationCodeLength | 8 | メールのコードの桁数 |
AuthenticationCodeExpirationPeriod | 300 | メールのコードの有効期間(秒) |
有効かどうかの判定
EnabledSecondaryAuthentication は次の順に判定します。
ModeがNoneなら無効。DefaultEnableでユーザーの「2 段階認証を無効化」(DisableSecondaryAuthentication)が立っていれば無効。DefaultDisableでユーザーの「2 段階認証を有効化」(EnableSecondaryAuthentication)が立っていなければ無効。- 拡張 SQL で
OnUseSecondaryAuthenticationがtrueのものが無ければ有効。 - 該当する拡張 SQL があれば、
@TenantIdと@UserIdを渡して実行します。どれか 1 つでも結果が 0 行、または 1 列目が false でない行を含めば有効です。すべての SQL が行を返し、その 1 列目がすべて false のときだけ無効になります。
拡張 SQL の書き方は 拡張 SQL の活用 を見てください。
認証コードの入力の流れ
図を読み込み中…
NotificationTypeがMailのときは常にメール方式になります。Totpのときも、コード入力画面の「メールで認証コードを受け取る」リンクからメール方式に切り替えられます(UsersController.AuthenticateのisAuthenticationByMail)。- TOTP の初回は、コードの検証に成功した時点で
EnableSecretKeyが立ち、以後は登録画面ではなく入力画面になります(HandlePostSecondaryAuthentication)。
メール方式
UpdateSecondaryAuthenticationCode が指定の文字種・桁数でコードを作り、Users.SecondaryAuthenticationCode に、有効期限を SecondaryAuthenticationCodeExpirationTime に保存します。コードはユーザーのメールアドレス(MailAddresses テーブル)すべてに送られます。検証は、保存したコードとの一致と有効期限内かどうかです(SecondaryAuthentication)。
WARNING
メールの認証コードは Users テーブルに平文で保存されます。TOTP のシークレットキー(Users.SecretKey)も Base32 の平文です。DB を参照できる人はこれらの値を読めます。
TOTP 方式
- シークレットキーは 20 バイトの乱数を Base32 にしたもので、
Users.SecretKeyに保存します(UpdateSecretKey)。登録画面の QR コードはotpauth://totp/{ログインID}?secret=...&issuer=Implem%20Pleasanterです。 - 検証は Otp.NET の
Totp(6 桁、30 秒ステップ)で行います。現在時刻で合わなければ 30 秒ずつ過去にずらして再検証し、CountTolerances回に達したら失敗です(VerifyTotp)。各回の照合には Otp.NET のRfcSpecifiedNetworkDelayの許容幅も加わります。
2 段階認証を通らないログイン
2 段階認証の判定は UserModel.Authenticate(ログインフォームの流れ)の中にあります。次のログインはこの流れを通らないため、2 段階認証を有効にしていても求められません。
| ログインの経路 | 理由 |
|---|---|
| SAML の SSO | SamlLogin が AllowAfterUrl で直接 Cookie を発行する |
| IIS の Windows 認証(SSO) | セッション開始時のミドルウェアでユーザーを確定する |
| TrustedProxy 認証 | ミドルウェアで直接 Cookie を発行する |
| パスキー | isAuthenticationByPasskey = true で 2 段階認証の判定を飛ばす |
パスキー(WebAuthn)
FIDO2 / WebAuthn によるパスワードレスのログインです。サーバー側は Fido2 / Fido2.AspNet 4.0.1、ブラウザ側は navigator.credentials を使います。
{
"PasskeyParameters": {
"Enabled": false,
"ServerName": "Pleasanter",
"ServerDomain": "localhost",
"Origins": [ "https://localhost:44331" ],
"UserVerificationRequirement": "Preferred"
}
}| 項目 | 内容 |
|---|---|
Enabled | false のときパスキーの API はすべて Not Found を返す |
ServerDomain | Relying Party ID |
ServerName | Relying Party の表示名 |
Origins | 許可するオリジン |
UserVerificationRequirement | Required / Preferred / Discouraged。それ以外や未指定は Preferred。登録時と認証時の両方に使う(PasskeyUserVerificationRequirement) |
登録と認証の設定
PasskeysController が Fido2 に渡す値は次のとおりです。
| 設定 | 値 | 意味 |
|---|---|---|
ResidentKey | Required | Discoverable Credential が必須。ログイン時にユーザー名を入れずに認証器からユーザーを特定する |
AttestationPreference | None | 認証器の証明を求めない(機種を制限しない) |
AuthenticatorAttachment | 指定なし | プラットフォーム認証器(Windows Hello、Touch ID など)と外部の認証器(USB・NFC・BLE のセキュリティキー)のどちらも使える |
ExcludeCredentials | 登録済みのパスキー | 同じ認証器の二重登録を防ぐ |
認証時の AllowedCredentials | 空 | Discoverable Credential でユーザーを特定する |
ブラウザ側(passkey.ts)でも、authenticatorAttachment が null なら undefined に置き換え、認証器の種類を制限しません。
登録の流れ
ログイン済みのユーザーが自分のパスキーを登録します。
図を読み込み中…
保存先は Passkeys テーブル(PasskeyId・CredentialId・UserId・Title・PasskeyData)で、PasskeyData には公開鍵、署名カウンタ、Transports、バックアップ可否・状態、Attestation の情報、AAGUID が JSON で入ります(PasskeyData.cs)。
ログインの流れ
図を読み込み中…
getassertionoptions と makeassertion は [AllowAnonymous] で、レート制限 AnonymousIp の対象です(PasskeysController.cs)。
ほかの認証方式との関係
isAuthenticationByPasskey = true の UserModel.Authenticate は、Provider 別の認証(パスワードの照合)、2 段階認証、パスワード有効期限のチェックを飛ばします。無効ユーザー(RevealUserDisabled 時)、RejectUnregisteredUser、ログイン有効期限、テナントの IP 制限、ロックアウトのチェックは通常どおり行います。このため、どの Provider でも、パスキーを有効にすればパスキーでログインできます。
ブラウザでのエラー表示は次のとおりです。
| 例外名 | 表示するメッセージ |
|---|---|
NotAllowedError | PasskeyOperationTimeoutOrAbort |
AbortError | PasskeyOperationAborted |
SyntaxError・TypeError・DataError・InvalidStateError | PasskeyResponseInvalid |
その他の Error | PasskeyOperationTimeoutOrAbort |
Error 以外 | PasskeyServerUnavailable |
TrustedProxy 認証
リバースプロキシが付けたヘッダーのログイン ID で、プリザンターにログインさせる仕組みです(TrustedProxyAuthenticationMiddleware)。
{
"TrustedProxyParameters": {
"Enabled": true,
"Header": "X-Forwarded-User"
}
}(X-Forwarded-User は例です。プロキシが付けるヘッダー名にします。)
Enabledがtrueのときだけミドルウェアが組み込まれます(UseAuthenticationの直後)。Headerが空、既に Cookie で認証済み、送信元がプロキシとして信頼できない、ヘッダーが空、のいずれかなら何もしません。- ヘッダーの値をログイン ID として、
Disabled = false・Lockout = falseのユーザーを検索します。 - 見つかれば認証タイプ
TrustedProxyのClaimsIdentityで Cookie を発行します(永続 Cookie)。
送信元の判定(IsTrustedProxy)には Security.json の ForwardedHeaders.KnownProxies(IP アドレス)と KnownNetworks(CIDR)を使います。比べる IP は、UseForwardedHeaders より前に記録した接続元(Connection.RemoteIpAddress)です。
WARNING
KnownProxies と KnownNetworks がどちらも空だと、どの送信元も信頼せず TrustedProxy 認証は動きません。起動時に SysLogs へ「[TrustedProxy] KnownNetworks/KnownProxies are empty.」の警告が出ます(Startup.cs)。プロキシを経由せず直接プリザンターに届く経路があると、ヘッダーを偽装されるおそれがあるため、信頼するプロキシは必ず絞ってください。