本人確認と認可

Transport、Endpoint、Service の Middleware を通じて既存の認証・認可システムを接続します。

Univer Collaboration SDK はユーザー、ロール、ACL モデルを定義しません。アプリケーションは Transport Middleware で信頼できる本人情報を確立し、Endpoint と Service の Middleware でリアルタイム Session とサーバー管理の共同編集データを保護します。

このページは Middleware と Events の具体例として、Middleware による本人確認と認可の統合を説明します。

編集・閲覧権限を確認する

前のサンプルを停止し pnpm example:permissions を実行します。独立したブラウザー環境で http://127.0.0.1:3010 を開き、片方は Alice · Editor、もう片方は Bob · Viewer でログインします。editor は編集でき、viewer は同期結果を読めますが編集できません。server/main.ts の transport.use()、readUnitData、joinUnit、submitChangeset を順に確認します。デモ Cookie とユーザー一覧を実際の認証・文書 ACL に置き換えてください。フロントエンドの読み取り専用状態だけでは不十分です。

Casey · No access でもログインできます。このアカウントでは文書の読み込みが拒否され、snapshot リクエストは HTTP 403 を返します。

ID モデル

識別子提供元意味とライフサイクル
userIDアプリケーション安定した業務上の ID。確定した変更セットの作成者
memberIDEndpoint1 つの WebSocket Session のオンラインメンバー ID。再接続すると変わる
sid + reqIdクライアント再試行でも変わらない変更セット送信の識別子

ブラウザーから送られるプロフィール、memberID、リビジョンはアプリケーションの認証を代替できません。安定したユーザーキーを context.userID に対応付けます。

Transport Middleware で本人情報を確立する

Transport は共同編集の HTTP リクエストごとに Middleware を実行します。アプリケーションは Cookie、Session、Bearer token を読み取り、認証結果を Context に追加できます。

TypeScript
transport.use(async (context, next) => {  const user = await auth.requireUser(context.incomingMessage);  context.userID = user.id;  context.customData.user = user;  context.customData.tenantID = user.tenantID;  context.customData.traceID = readTraceID(context.incomingMessage);  await next();});

認証に失敗した場合は next() を呼ばずに HTTP レスポンスを終了します。

TypeScript
transport.use(async (context, next) => {  const user = await auth.findUser(context.incomingMessage);  if (!user) {    context.response.statusCode = 401;    context.response.end("Authentication required");    return;  }  context.userID = user.id;  await next();});

SDK は Cookie 名、トークン形式、ユーザーテーブル、ログインフローを指定しません。

WebSocket Session への本人情報の引き継ぎ

WebSocket の経路はクライアントのペイロード内のユーザーフィールドを信頼しません。Endpoint は使い捨ての Session Ticket により、認証済み HTTP Context をリアルタイム Session に引き継ぎます。

text
Session Ticket の HTTP リクエスト  → Transport Middleware で認証  → Endpoint が { userID, customData } を保存  → 不透明な使い捨てチケットを返却  → WebSocket 接続時にチケットを消費  → Session { userID, memberID, customData } を作成

チケット文字列には userID や customData は含まれません。memberID は現在の接続のみを識別し、独立した認証情報ではありません。複数ノードで共有チケットストアを使う場合、保存される customData はシリアライズされるため、JSON にできる内容にしてください。

適切な境界で認可する

保護する操作Middleware
共同編集の HTTP 入口Transport use()
WebSocket 接続Endpoint connect
Unit Room への参加Endpoint joinUnit
スナップショット、ブロック、変更セットの読み取りService readUnitData
内容の編集の送信Service submitChangeset
Unit の作成Service createUnit
Unit の削除Service deleteUnits
Unit の復元Service recoverUnits

Endpoint の joinUnit は Session がリアルタイム Room に参加できるかだけを制御します。スナップショットや不足する変更セットは HTTP で直接読めるため、JOIN の検査は readUnitData の代わりにはなりません。同様に、クライアントの読み取り専用 UI は製品上の表示であり、サーバー側の submitChangeset ポリシーを代替しません。

読み取り、JOIN、編集を保護する

TypeScript
import { CollabError } from "@univerjs-pro/collaboration-service";endpoint.use("joinUnit", async (context, next) => {  const allowed = await acl.canRead(context.session.userID, context.unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Cannot join this Unit");  }  await next();});service.use("readUnitData", async (context, next) => {  const allowed = await acl.canRead(context.userID, context.request.unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Unit is not accessible");  }  await next();});service.use("submitChangeset", async (context, next) => {  const unitID = context.request.changeset.unitID;  const allowed = await acl.canEdit(context.userID, unitID);  if (!allowed) {    throw new CollabError("PERMISSION_DENIED", "Unit is read-only");  }  await next();});

通常のアプリケーションでは、HTTP と JOIN の読み取りポリシー、および送信の編集ポリシーの 3 つを設定します。

Unit のライフサイクルを保護する

共同編集プロトコルは既存の Unit を開きます。「ドキュメント作成」は通常、製品レコードと ACL を作成してから createUnitFromData() または createUnitFromSnapshot() を呼ぶアプリケーション API です。Service Middleware でもライフサイクルを保護します。

TypeScript
service.use("createUnit", async (context, next) => {  if (!(await acl.canCreate(context.userID, context.request.snapshot.type))) {    throw new CollabError("PERMISSION_DENIED", "Unit creation denied");  }  await next();});service.use("deleteUnits", async (context, next) => {  for (const unitID of context.request.unitIDs) {    if (!(await acl.canDelete(context.userID, unitID))) {      throw new CollabError("PERMISSION_DENIED", `Cannot delete ${unitID}`);    }  }  await next();});

recoverUnits は個別に保護してください。過去に削除権限があったユーザーが、いつまでも復元権限を持つとは限りません。

customData で検索結果を再利用する

同じ Service 呼び出し内の Middleware は customData で重複クエリを避けられます。

TypeScript
service.use("submitChangeset", async (context, next) => {  const unitID = context.request.changeset.unitID;  context.customData.role ??= await acl.getRole(context.userID, unitID);  if (context.customData.role === "viewer") {    throw new CollabError("PERMISSION_DENIED", "Unit is read-only");  }  await next();});

customData は現在の呼び出しまたは Session にのみ属し、自動的には永続化されません。ロール、ACL、テナント関係はアプリケーションのストレージで管理します。

拡張モジュールには個別のポリシーが必要

History、Thread Comment、Worktree は独立した Service Middleware を持ちます。

  • History の一覧と変更セットの読み取りでは Unit の閲覧権限を確認します。
  • コメント一覧では閲覧権限を、追加・返信・編集・削除ではコメントのポリシーを適用します。
  • Worktree では可視性、下書き編集、状態変更、マージを個別に保護します。
  • Worktree のトランクへのマージはトランクの Service Middleware を通ります。

基本の Collaboration Service のルールはこれらのモジュールを自動的には保護しません。拡張ごとに別の ACL モデルを作らず、同じアプリケーションのポリシーサービスを再利用してください。

クライアントの現在のユーザーを設定する

UserManagerService.setCurrentUser() は現在の Univer インスタンスのユーザー ID、名前、アバターを設定し、フロントエンドコンポーネントが参照できるようにします。たとえばコメントパネルの「自分に関連」フィルターは、この ID で現在のユーザーに関連するコメントを表示します。

Univer インスタンス作成後、文書を読み込む前に設定します。

TypeScript
import { UserManagerService } from "@univerjs/core";// currentUser is your application profile; id/name/avatar are example field names.univer  .__getInjector()  .get(UserManagerService)  .setCurrentUser({    userID: currentUser.id,    name: currentUser.name,    avatar: currentUser.avatar ?? "",  });

アプリケーションの安定したユーザー ID を userID に対応付け、サーバーで認証した context.userID と一致させます。共同編集中に共有する名前とアバターは、Presence の説明に従い、サーバーの connect Middleware で設定します。

実行可能な全体の実装は権限のサンプルを参照してください。

このガイドはいかがですか?

© 2026 DreamNum Co., Ltd.