認証基盤(Cookie・API キー・Bearer)
プリザンターは ASP.NET Core の Cookie 認証だけを認証スキームとして登録し、API キーは独自にリクエストボディから読み取ります。どちらの経路でも、最終的には Context のコンストラクタが Users テーブルを検索してユーザーを確定します。
- Cookie にはログイン ID しか入らず、認証チケット自体はサーバー側(セッションストア)に保存されます。
TenantId・UserId・DeptIdなどはリクエストごとに Users テーブルから読み直します。Authorization: Bearerヘッダーは JWT ではなく API キー(ファイルのアップロード API)か SCIM トークンの受け渡しに使われるだけです。JWT Bearer 認証は登録されていません。
ログイン時の認証方式(Provider・2 段階認証・パスキーなど)は 認証方式の内部動作 を見てください。
全体像
図を読み込み中…
Cookie 認証
Startup.cs の設定
Startup.ConfigureServices の要点は次のとおりです。
| 設定 | 内容 |
|---|---|
| グローバルフィルタ | AuthorizeFilter(RequireAuthenticatedUser)と CheckContextAttributes を全コントローラーに追加 |
| 認証スキーム | CookieAuthenticationDefaults.AuthenticationScheme("Cookies")のみ。SAML のときは AddSaml2 も追加 |
| ログインパス | /users/login |
| Cookie の有効期限 | Session.json の RetentionPeriod(分) |
| 認証エラー時 | SAML 以外で Security.json の ShowLoginPageOnAuthError が false なら、ログイン画面へリダイレクトせず 404 を返す |
| チケットの保存 | ITicketStore に AuthenticationTicketStore を登録 |
グローバルの AuthorizeFilter があるため、[Authorize] を書かなくても全コントローラーで Cookie 認証が必要になります。認証なしで受けたいアクションには [AllowAnonymous] を付けます。本体の API コントローラー(ItemsController など)はすべて [AllowAnonymous] と [CheckApiContextAttributes] を付けたうえで、アクションの中で context.Authenticated を確かめています(ItemsController.cs)。
ログイン時の Cookie 発行
ログインに成功すると Context.FormsAuthenticationSignIn が呼ばれます。セッション GUID を振り直し(RotateSessionGuid)、クレーム ClaimTypes.Name(値はログイン ID)だけを持つ認証タイプ Forms の ClaimsIdentity で SignInAsync します。「ログインしたままにする」を選んだときは永続 Cookie になります。
図を読み込み中…
TenantId・UserId・DeptId は Cookie に入りません。
認証チケットのサーバー側保存
AuthenticationTicketStore は ITicketStore の実装です。
StoreAsync: 新しい GUID を作り、シリアライズしたチケットを Base64 にしてセッションデータのキーAuthenticationTicketに保存し、GUID を返す(Cookie にはこの GUID を元にした値だけが入る)RetrieveAsync/RenewAsync: GUID でセッションデータを読み書きするRemoveAsync: セッションデータから削除し、Session.jsonのUseKeyValueStoreがtrue(Redis)なら Redis からも消す
セッションデータの保存先は DB(Sessions テーブル)か Redis です。性能面の話は 同時アクセスが遅くなる理由(セッション・Redis) にあります。
Context によるユーザーの解決
new Context() のたびに、次の 3 段階でユーザーが決まります。
SetRequests:HttpContext.User.IdentityからLoginId(Name)、AuthenticationType、IsAuthenticated、クレーム、IdentityType(Identity の型名)を取り出すSetUserProperties: リクエストの本文をApiクラスにデシリアライズし、ApiKeyがあればApiKeyで、無ければLoginIdで Users を検索するSetUser: 見つかればAuthenticated = trueにして、TenantId・DeptId・UserId・Language・UserSettings・HasPrivilegeなどを設定する
Users の検索は GetUser を通り、必ず Disabled = false と Lockout = false が条件に付きます。無効化・ロックアウトされたユーザーは、Cookie でも API キーでも Authenticated = false になります。
補足:
LoginIdでの検索では、特権ユーザーがユーザー切り替え中(セッションのSwitchLoginId)ならそのログイン ID を、テナント切り替え中ならその切り替え先を使います。SetUserは、ユーザーのテナントが保護テナントでなく、マルチテナントのライセンスも無い場合にAuthenticatedをfalseに戻します。
IsAuthenticated と Authenticated
| プロパティ | 意味 |
|---|---|
IsAuthenticated | ASP.NET Core の Cookie(HttpContext.User)が認証済みか |
Authenticated | Users テーブルで有効なユーザーが見つかったか |
Cookie が有効でも、その後にユーザーが削除・無効化・ロックアウトされると Authenticated は false です。この状態の画面リクエストは、CheckContextAttributes がサインアウトさせてログイン画面へリダイレクトします(後述)。
権限情報
Context の生成時には SetPermissions で PermissionHash(サイトごとの権限)と所属グループを読み込みます。HasPrivilege は Security.json の PrivilegedUsers にログイン ID が含まれるかで決まり(Permissions.PrivilegedUsers)、特権ユーザーは権限チェックをバイパスします。権限チェックの詳細は アクセス権限の実装 を見てください。
画面用のフィルタ(CheckContextAttributes)
CheckContextAttributes はグローバルフィルタなので、/api/ のコントローラーや拡張ライブラリのコントローラーにも掛かります。上から順に次を確認します。
| # | 内容 | 結果 |
|---|---|---|
| 1 | パラメータファイルに構文エラーがある(errors コントローラー以外) | エラーページへのリダイレクトを設定(処理は続く) |
| 2 | Security.json の AllowIpAddresses による IP 制限(/api/ と /scim/ は対象外) | 403 |
| 3 | 認証済みでテナントの IP 制限(契約設定)を満たさない | サインアウトして InvalidIpAddress へ |
| 4 | 認証済みでテナントの契約期限切れ | サインアウトしてログイン画面へ(?expired=1) |
| 5 | 認証済み・TokenCheck が有効・フォーム送信で Token が一致しない | 400 |
| 6 | Cookie は認証済みだが Users に有効なユーザーが無い(samllogin 以外) | Windows 認証なら空の応答、それ以外はサインアウトしてログイン画面へ |
| 7 | アップロードされたファイル名が不正 | 400 |
API 用のフィルタ(CheckApiContextAttributes)
CheckApiContextAttributes はグローバルではなく、API コントローラーに属性として付けて使います。本文を読み取り(EnableBuffering で読み直せるようにする)、その本文で Context を作って次を確認します。
図を読み込み中…
InvalidJsonData は、Content-Type が JSON で本文が空でないのに Api クラスへデシリアライズできなかったときに true になります。CSRF トークンの確認は Cookie で認証済みのリクエストだけが対象で、API キーだけのリクエストでは行いません。
API キー認証
送り方
API キーはリクエスト本文の JSON に ApiKey として入れます。本文は Api クラス(ApiVersion・ApiKey・View・Keys・Offset・PageSize・Token など)にデシリアライズされます。
{
"ApiVersion": 1.1,
"ApiKey": "(API キー)",
"Offset": 0
}本文に ApiKey があればそれが最優先で、無ければ Cookie の LoginId でユーザーを決めます。ブラウザのスクリプトから API を呼ぶときに ApiKey を省略できるのはこのためです(TokenCheck が有効なら Token が必要)。
API 利用の許可
API キーでのユーザー特定の時点では、API の利用可否は確かめません。各 API のバリデータ(Validators.ValidateApi など)が、次のどれかに当たれば 403 を返します。
Api.jsonのEnabledがfalse- テナントの契約設定で API が無効
UserSettings.AllowApiがfalse
AllowApi の判定は次のとおりです。
| 条件 | 結果 |
|---|---|
| 特権ユーザー | 許可 |
User.json の DisableApi またはテナントの DisableApi が有効で、ユーザーの「API の利用を許可」(Users.AllowApi)が false | 拒否 |
UserSettings.DisableApi が true | 拒否 |
| それ以外 | 許可 |
UserSettings.DisableApi はクラスに定義されていますが、DB に書き込む RecordingJson には含まれていないため、保存されません(UserSettings.cs)。
ユーザーの「無効」(Disabled)は、ログインと API キー認証の両方を止めます。「画面にはログインさせず API だけ使わせる」設定は本体にはありません(改修案は API 専用ユーザー)。
API キーの発行
API キーは各ユーザーが「API 設定」画面(/users/editapi)で発行・削除します。発行時の値は GUID を SHA-512 でハッシュした文字列で、Users テーブルの ApiKey 列にそのまま保存されます(UserModel.CreateApiKey)。照合は Users.ApiKey との完全一致で、有効期限はありません。
- 発行・削除の処理(
UserUtilities.CreateApiKey/DeleteApiKey)は対象をcontext.UserId(ログイン中の本人)に固定しています。管理者が他のユーザーの API キーを発行・削除する画面はありません。 - 「API 設定」メニューは、
Api.jsonのEnabledがtrue、テナントで API が無効でない、AllowApiがtrueのときに表示されます(HtmlNavigationMenu.cs)。
Bearer ヘッダー
Startup.cs に AddJwtBearer は無く、JWT による認証はできません。Authorization: Bearer ヘッダーを受け取るのは次の箇所です。
| 箇所 | トークンの中身 | 検証 |
|---|---|---|
ファイルのアップロード API(/api/binaries/upload) | プリザンターの API キー | 通常の API キー認証に合流 |
SCIM(/scim/) | SCIM トークン | ScimTokens テーブルのハッシュと有効期限で検証 |
| API のレート制限 | API キー | 利用者の識別にだけ使う(認証ではない) |
ファイルのアップロード API
multipart/form-data では本文に JSON を入れられないため、BinariesController.Upload は Authorization ヘッダーから API キーを取り出し、{"ApiKey": "..."} という JSON を組み立てて Context の apiRequestBody に渡します。以降は通常の API キー認証と同じです。ヘッダーの取り出し(AuthorizationHeaderValue)は、小文字にして bearer で始まるかを見て、"bearer " の長さ分を切り取ります。使い方は ファイルのみのアップロード・大容量ファイル にあります。
SCIM
CheckScimContextAttributes が Authorization: Bearer のトークンを ScimTokenValidator で検証します。トークンの SHA-256 ハッシュで ScimTokens テーブルを検索し、無効化されていない・期限切れでないものが見つかれば、そのトークンのテナント ID とユーザー ID を Context に設定します。API キーとは別の仕組みです。
外部サービスへの送信
LINE 通知(Line.cs)や AI 連携なども Authorization: Bearer を付けますが、これは外部サービスへ送るリクエストで、プリザンターの認証とは関係ありません。
一般的な JWT Bearer 認証との違い
| プリザンター(API キー) | 一般的な JWT Bearer | |
|---|---|---|
| ASP.NET Core の認証ハンドラー | なし | AddJwtBearer |
| トークン | SHA-512 の 16 進文字列(中身に意味はない) | 署名付き JWT |
| 検証 | Users.ApiKey との一致 | 署名・発行者・対象・有効期限 |
| 発行 | 本人が「API 設定」画面で発行 | トークンエンドポイント |
| 有効期限 | なし(削除・再発行するまで有効) | exp で失効 |
| 送る場所 | 本文の ApiKey(アップロード API だけヘッダー) | Authorization ヘッダー |
| 権限の範囲 | ユーザーの権限すべて | スコープで絞れる |
JWT / OAuth 2.0 を追加する場合の改修案は API 認証への JWT・OAuth 2.0 の追加 にまとめています。
関連パラメータ
| パラメータ | 内容 |
|---|---|
Session.json の RetentionPeriod | Cookie とセッションの有効期限(分) |
Session.json の UseKeyValueStore | セッションを Redis に保存するか |
Security.json の AllowIpAddresses / IpRestrictionExcludeMembers | サーバー全体の IP 制限と除外メンバー |
Security.json の TokenCheck | CSRF トークンの確認(既定 false) |
Security.json の PrivilegedUsers | 特権ユーザーのログイン ID |
Security.json の SecureCookies | セッション Cookie などに Secure 属性を付けるか |
Security.json の ShowLoginPageOnAuthError | 未認証時にログイン画面へリダイレクトするか(false なら 404。SAML 時は無効) |
Api.json の Enabled | API 全体の有効・無効 |
User.json の DisableApi | 既定で API を使わせないか(ユーザーの「API の利用を許可」で個別に許可) |