Skip to content

API 専用ユーザー(画面ログイン不可・API キーのみ) ​

第1版作成 最終更新 (日本時間)
確認バージョン1.5.8.1

システム連携用に「画面にはログインしないが、API キーでだけ操作するユーザー」(サービスアカウント)を作りたい場合の検討です。

本体の標準機能ではありません

1.5.8.1 のプリザンターに API 専用ユーザーの機能はありません。以下は現行の実装(1.5.8.1)を前提にした改修案です。

前提にした現行の実装 ​

認証の仕組みの詳細は 認証基盤(Cookie・API キー・Bearer) にあります。この検討に関係するのは次の点です。

項目現行の動作
API 専用のフラグ無い。Users の関連列は AllowApi(API の利用を許可)・ApiKey・Disabled・Lockout・Password
「無効」(Disabled)ログイン(GetByCredentials)と API キー認証(Context.GetUser)の両方を止める。「画面は不可・API は可」には使えない
AllowApiAPI の利用可否だけ。ログインには影響しない
API キーの発行・削除「API 設定」画面で本人だけができる。UserUtilities.CreateApiKey / DeleteApiKey は対象を context.UserId に固定している
UserSettings 列JSON の文字列。DB に書かれるのは RecordingJson が明示的に書き出すプロパティだけ

API キーは「そのユーザーの分身」で、ユーザーから独立したサービスアカウントという考え方はありません。

案 1: 推測できないパスワードにする(改修なし) ​

  1. テナント管理者がユーザーを作り、「API の利用を許可」をオンにする
  2. 一時的なパスワードを設定する
  3. そのユーザーでログインし、「API 設定」画面で API キーを発行する
  4. パスワードを十分に長いランダムな文字列に変え、誰にも共有しない
  5. API キーを連携先に設定する
制約内容
API キーの再発行・削除同じ手順で本人としてログインし直す必要がある
ログインの防止パスワードを知らなければ実質的にログインできないだけで、仕組みとしては止めていない
パスワード有効期限期限が切れるとログイン時にパスワード変更を求められる(API には影響しない)
ライセンス数有効なユーザーとして数えられる

案 2: 管理者が API キーを代理で発行・削除できるようにする ​

テナント管理者(またはユーザー管理ができるユーザー)が、ユーザー編集画面から他のユーザーの API キーを発行・再発行・削除できるようにします。

ファイル改修内容
UserUtilities.csCreateApiKey / DeleteApiKey で対象の userId を受け取る。ユーザー編集画面に API キーのタブを追加する
UserValidators.csOnApiCreating / OnApiDeleting に、本人以外が対象のとき Permissions.CannotManageUsers と対象ユーザーの AllowApi を確かめる分岐を追加する
UsersController.cs各アクションで対象の userId を受け取る
改修イメージ
csharp
// UserUtilities.CreateApiKey(改修後のイメージ)
public static string CreateApiKey(Context context, SiteSettings ss, int userId = 0)
{
    var targetUserId = userId > 0 ? userId : context.UserId;
    var userModel = new UserModel(context: context, ss: ss, userId: targetUserId);
    var invalid = UserValidators.OnApiCreating(
        context: context,
        userModel: userModel,
        targetUserId: targetUserId);
    // 以降は現行と同じ
}

// UserValidators.OnApiCreating(改修後のイメージ)
public static ErrorData OnApiCreating(
    Context context, UserModel userModel, int targetUserId = 0)
{
    if (!Parameters.Api.Enabled || context.ContractSettings.Api == false)
    {
        return new ErrorData(type: Error.Types.InvalidRequest);
    }
    if (targetUserId > 0 && targetUserId != context.UserId)
    {
        // 代理操作: ユーザー管理の権限と、対象ユーザーの AllowApi を確認
        if (Permissions.CannotManageUsers(context: context))
        {
            return new ErrorData(type: Error.Types.HasNotPermission);
        }
        if (!userModel.AllowApi)
        {
            return new ErrorData(type: Error.Types.InvalidRequest);
        }
    }
    else if (context.UserSettings?.AllowApi(context: context) == false)
    {
        return new ErrorData(type: Error.Types.InvalidRequest);
    }
    if (userModel.AccessStatus != Databases.AccessStatuses.Selected)
    {
        return new ErrorData(type: Error.Types.InvalidRequest);
    }
    return new ErrorData(type: Error.Types.None);
}

現行の Permissions.CannotManageUsers は、EnableManageTenant(テナント管理者)でも特権ユーザーでもなく、Service.json の ShowProfiles も false のときに true を返します。

図を読み込み中…

案 3: UserSettings に ApiOnly フラグを追加する ​

UserSettings(JSON の列)に ApiOnly を追加し、ログインを明示的に拒否します。JSON の列なので DB のスキーマ変更(列の追加)は要りません。Newtonsoft.Json は既定で未知のプロパティを無視し、JSON に無いプロパティは既定値(bool? なら null)になるため、既存のデータとも互換性があります。

ファイル改修内容
UserSettings.cspublic bool? ApiOnly; を追加し、RecordingJson に if (ApiOnly == true) us.ApiOnly = ApiOnly; を追加
UserModel.csログインフォームの Authenticate(returnUrl 版)の先頭で ApiOnly のユーザーを拒否する
UserUtilities.cs / UsersController.csテナント管理者が ApiOnly を切り替える画面とアクション
改修イメージ
csharp
// UserModel.Authenticate(returnUrl 版)の先頭に追加するイメージ
if (IsApiOnlyUser(context: context))
{
    return Deny(context: context); // 通常の認証失敗と同じ応答にする
}

private bool IsApiOnlyUser(Context context)
{
    var userSettings = Repository.ExecuteScalar_string(
        context: context,
        statements: Rds.SelectUsers(
            column: Rds.UsersColumn().UserSettings(),
            where: Rds.UsersWhere()
                .Add(
                    name: "LoginId",
                    value: LoginId,
                    raw: "(lower(\"Users\".\"LoginId\") = lower(@LoginId))")
                .Disabled(false)))
        ?.Deserialize<UserSettings>();
    return userSettings?.ApiOnly == true;
}

// テナント管理者が ApiOnly を切り替えるアクションのイメージ
public static string SetApiOnly(Context context, SiteSettings ss, int userId)
{
    if (Permissions.CannotManageUsers(context: context))
    {
        return Messages.ResponseHasNotPermission(context: context).ToJson();
    }
    var userModel = new UserModel(context: context, ss: ss, userId: userId);
    if (userModel.AccessStatus != Databases.AccessStatuses.Selected)
    {
        return Messages.ResponseNotFound(context: context).ToJson();
    }
    userModel.UserSettings.ApiOnly = context.Forms.Bool("ApiOnly");
    Repository.ExecuteNonQuery(
        context: context,
        statements: Rds.UpdateUsers(
            where: Rds.UsersWhere()
                .TenantId(context.TenantId)
                .UserId(userId),
            param: Rds.UsersParam()
                .UserSettings(userModel.UserSettings.RecordingJson()),
            addUpdatorParam: false,
            addUpdatedTimeParam: false));
    return new ResponseCollection(context: context).ToJson();
}

API キー認証(Context.GetUser)の条件は Disabled = false と Lockout = false だけで、UserSettings の中身は見ないため、ApiOnly = true でも API キー認証はそのまま使えます。

注意点:

  • ApiOnly のユーザーは自分で「API 設定」画面を開けないため、案 2(管理者による代理発行)と組み合わせる必要があります。
  • ログインフォームを通らない経路(パスキー、SAML の SSO、IIS の Windows SSO、TrustedProxy 認証)も止めたい場合は、それぞれの経路にも同じチェックが要ります。パスキーは UserModel.Authenticate を通るため先頭のチェックで止まりますが、SAML の SSO などは AllowAfterUrl で直接 Cookie を発行します(認証方式の内部動作)。
  • UserSettings はセッションにも読み込まれるため、切り替えをすぐに反映させるにはセッションの扱いも考える必要があります。

比較 ​

観点案 1案 2案 3
本体の改修なし小(4 ファイル程度)小(3〜5 ファイル)
DB スキーマの変更なしなしなし(JSON の列を使う)
ログインの防止パスワードを秘匿するだけ同左フラグで拒否
API キーの発行・再発行本人としてログインが必要管理者が代理でできる案 2 と組み合わせて管理者が行う
バージョンアップ時影響なし改修の追従が必要改修の追従が必要

画面ログインを確実に止めたいなら、案 2 と案 3 の組み合わせになります。

関連ページ ​

変更履歴

第1版認証方式(2 段階認証・パスキー・LDAP・フォールバック)と認証基盤の解説、関連する改修・設計メモを追加