KUMODeck
EN

認証

利用者(API の名前では players = プレイヤー)は、登録する前にアプリやゲームを使い始められます。Kumo.init()は初回の訪問でゲストを作り、以後の訪問ではそのゲストを復元します。データを残したくなった利用者は、同じアカウントにメール・Google・Discord・Apple・Xを連携し、すべてをそのまま引き継げます。

ゲスト(自動)#

const kumo = await Kumo.init({ projectKey: 'pk_live_…' });
kumo.auth.player;        // { id, displayName, isGuest: true, createdAt, identities: [{ provider: 'guest' }] }
await kumo.auth.setDisplayName('Ace');   // ルームで表示される名前

先に自分のログイン画面を見せたい場合は、initにautoGuest: falseを渡します。セッションは公開鍵ごとにlocalStorageへ保存されるので、devとprodが混ざることはありません。ストレージが使えない場合(プライベートモードや一部のiframe)、SDKはメモリで代用し、利用者は再読み込みのたびに新しいゲストになります。

メールとパスワード#

// 今のゲストを永続アカウントにする — データはそのまま
await kumo.auth.linkEmail('[email protected]', 'correct horse battery');

// 別の端末で
await kumo.auth.signInWithEmail('[email protected]', 'correct horse battery');

// ゲストを経ずに新しいアカウントを作る
await kumo.auth.signUpWithEmail('[email protected]', 'correct horse battery', 'Ace');

await kumo.auth.signOut();
kumo.auth.onChange((player) => renderAccount(player));   // ログアウト時は null

パスワードは8文字以上が必要です。ログインのエラーから、そのメールが登録済みかどうかが漏れることはありません。

登録画面の「KUMODeckからのお知らせ」の欄(既定で出ます)#

メールアドレスの登録画面には、KUMODeckが描く欄がもう1つ出ます(「KUMODeckからのお知らせを受け取る」=新しいゲームや機能のご案内・月2回まで)。既定で出る欄なので、自分で登録画面を作る場合も画面に入れてください。 アプリごとに外すこともできます(下)。

const news = await kumo.news.mountNewsOptIn(document.querySelector('#news-box'));  // 欄を描く(出さない場合は何も描かない)
await kumo.auth.signUpWithEmail(email, password);
await news.submit(email);   // 利用者がチェックしていなければ何もしない

あなたにも利用者にも困らない理由:

  • 利用者が選べます。 別のチェックボックスで、最初はチェックなしです。チェックしなくても登録は同じようにできます。
  • 確認メールで決まります。 KUMODeckの確認メールのリンクを押すまで、何も送られません。
  • あなたに費用はかかりません。 この名簿の費用はKUMODeckが持ち、あなたの請求には載りません。
  • 大人にだけ出ます。子ども向けのアプリやゲーム(audience: "kids")では出ません。欄の文言(KUMODeckの名前と「このアプリを動かしているサービス」)は正しさを保つためにKUMODeckが描くので、自分で書かないでください。

この欄を自分のアプリから外すには、ダッシュボード(概要 →「KUMODeckからのお知らせの欄」)でOFFにするか、kumo.config.jsonで外して反映します:

kumodeck features off kumoNews && kumodeck config push --env production   # "kumoNews": { "optIn": false } を書きます

コードはそのままで大丈夫です。外すとmountNewsOptInは何も描かず、submitも何もしません。すでに登録した利用者には、本人が止めるまでKUMODeckのお知らせが届きます。もう一度出すにはkumodeck features on kumoNews。

セッション#

トークン有効期間保存先
アクセストークン(JWT)15分メモリ + localStorage
リフレッシュトークン90日、使うたびに回転localStorage

トークンを自分で扱う必要はありません。SDKはtoken_expiredで更新し、1回だけ再試行します。同じアプリのタブ同士はWeb Locks APIで調整するので、2つのタブが同じリフレッシュトークンを取り合うことはありません。使用済みのリフレッシュトークンを再提示すると、セッションの系列ごと失効します(盗まれたトークンは、どちらかが再び使った時点で無効になります)。

サインアウトで止まるのはリフレッシュトークンです。すでに渡したアクセストークンは期限まで、最大15分使えます(署名だけで確かめ、DBを引かないため)。SDKはサインアウトで捨てるので、トークンが別の所に写されたときだけ関係します。BANは利用者の書き込みをすぐに止めます。読むのは次のリフレッシュまで(最大15分)使え、そのリフレッシュが失敗してサインアウトになります。

Google・Discord・Apple・Xでログイン#

どのプロバイダーも、kumo.config.jsonでONにするまでは無効です。

kumo.config.json
{
  "auth": {
    "redirectUrls": ["https://mygame.example.com/*"],
    "providers": {
      "google": { "enabled": true },
      "x": { "enabled": true }
    }
  }
}
プロバイダー用意するもの
X自分のXアプリを登録し、そのOAuth 2.0のClient IDとClient Secretをダッシュボードのサインイン方法→ Xに入力します。手順は自分のXアプリを参照してください
Google・Discord・Appleプロバイダー側でOAuthクライアントを作り、client idとsecret(Appleの場合はServices ID、Team ID、Key ID、秘密鍵)をダッシュボードのサインイン方法に入力します。登録するリダイレクトURIはそこに表示されます(…/v1/auth/oauth/<provider>/callback)。認証情報は暗号化して保存され、設定ファイルに入ることはありません
// 既定はポップアップ — クリックのハンドラから呼ぶ
await kumo.auth.signInWithProvider('google');
await kumo.auth.signInWithProvider('discord', { mode: 'redirect' });   // アプリ内ブラウザ向け
await kumo.auth.linkProvider('apple');                                 // 今のゲストに連携し、進捗を残す

// リダイレクトの後、結果は Kumo.init() が受け取る。読むには:
const result = await kumo.auth.completeRedirectSignIn();   // { player, provider, linked } または null
  • 戻り先URLはauth.redirectUrlsで許可したものだけです(完全一致のURL、またはパスの前方一致を表す末尾の*)。development中のlocalhostと、KUMODeckがホスティングするURLは自動で許可されます。これにより、攻撃者が自分のサイトでログイン用のコードを受け取ることを防ぎます。
  • OAuthはPKCEを使います。URLのフラグメントで戻ってくるコードは、利用者のブラウザにしか存在しないverifierがなければ役に立ちません。
  • アカウントがメールアドレスで統合されることはありません。すでに別の利用者に属しているIDを連携しようとすると、identity_already_linkedで失敗します。
  • Xのアプリ内ブラウザでは、ポップアップが安定しないため、既定のモードはredirectです。
  • kumo.auth.unlinkIdentity(provider)はログイン手段を外します(身に覚えのない連携を消すときなど)。この端末以外はすべてログアウトされます。最後のログイン手段は外せません: 外した後にほかのプロバイダーもパスワード付きのメールも残らない場合は、409 last_login_methodで失敗し、何も変わりません。外せてしまうと、この端末がログアウトした時点でアカウント(セーブも)に二度と入れなくなるためです。利用者には、先に別の手段を付けてから(linkEmail / linkProvider)古いほうを外すよう案内してください。

``js try { await kumo.auth.unlinkIdentity('google'); } catch (e) { if (e instanceof KumoError && e.code === 'last_login_method') showHint('先に別のログイン手段を追加してください'); else throw e; } ``

自分のXアプリ#

Xでログインは、ほかのプロバイダーと同じく、自分で登録したXアプリで動きます。登録して保存するまでは、Xでログインを始めると409 provider_not_configuredで失敗します。作業は10〜20分で、アプリ(またはスタジオ)ごとに1回です。

  1. console.x.comに自分のXアカウントでサインインし、Developer AgreementとPolicyに同意して、用途を入力します(例:「自分のWebアプリの利用者がXのアカウントでログインするため。公開プロフィールを読むだけで投稿はしない」)。
  2. 従量課金(Pay Per Use)の本番(Production)環境でアプリを作ります。旧Free / 開発環境のアプリはXに拒否されます(403 client-not-enrolled)。
  3. 名前・説明・アイコンはアプリの名前でかまいません(Xの同意画面にこの名前が出ます)。どれにも「X」「Twitter」やXのロゴを入れないでください。
  4. User authentication settingsでOAuth 2.0を有効にし、種別はWeb App(confidential client)にします。PKCEはKUMODeckが付けるので、設定はいりません。
  5. スコープはusers.readとtweet.readだけにします。
  6. コールバックURLには、ダッシュボードのサインイン方法→ Xに表示される…/v1/auth/oauth/x/callbackのURLを、表示どおり完全一致で登録します。
  7. Webサイト・利用規約・プライバシーポリシーのURLを入力します。プライバシーポリシーには、Xから受け取る情報(@handle・名前・アイコン・XのユーザーID)を書いてください。
  8. OAuth 2.0のClient IDとClient Secretを生成し(Secretは一度しか表示されません)、ダッシュボードのサインイン方法→ Xに貼り付けて保存したら、auth.providers.x.enabledをONにします。

XのAPIの利用分は、Xから自分のXアプリに直接請求されます。KUMODeckは請求しません。Xによると、ログインのために利用者本人のプロフィール(/2/users/me)を読むのは無料です。

Xのログインの規則は、アプリの画面にもかかります。Xでログインのボタンをほかのログイン方法と少なくとも同じくらい目立たせ、ログイン後に本人のXの@handle・アイコン・Xのロゴを見せ(kumo.x.profile())、ログインの前にプライバシーポリシーへのリンクを見せてください。

KUMODeckの共用Xアプリが使える環境では、ダッシュボードに自分のアプリの代わりに設定なしで使うの選択肢も出ます。Xで共有するを参照してください。

メールの確認とパスワードの再設定#

メールのリンクはアプリのページに戻ってきます(そのページはauth.redirectUrlsに入っている必要があります)。redirectUrlを渡さない場合、リンクはアプリのドメインにある小さなブランドなしの組み込みページ(/.auth/verify、/.auth/reset)を開き、そこで処理を終えます。アプリのURLを渡せば、自分の画面で処理できます。

await kumo.auth.resendVerification();                     // ログイン中のメールアカウント向け
await kumo.auth.sendPasswordReset('[email protected]');     // 常に成功する(メールの有無を明かさない)

// 再設定メールからページが開かれたとき:
const pending = kumo.auth.pendingAction();                // { type: 'reset_password', token } または null
if (pending) await kumo.auth.resetPassword(pending.token, newPassword);

確認リンクは24時間、再設定リンクは30分有効で、それぞれ一度だけ使えます。パスワードを再設定すると、利用者はすべての端末でログアウトされます。メールが確認済みかどうかはkumo.auth.player.emailVerifiedでわかります。

データのエクスポートとアカウントの削除#

アプリストアはアプリ内でのアカウント削除を求め、プライバシー関連の法律は利用者に自分のデータの写しを受け取る権利を与えています。両方を設定画面に置いてください。

const data = await kumo.account.exportData();       // このプレイヤーについて保存しているすべて(JSON。パスワードのハッシュは含まない)
await kumo.account.delete({ password });            // 取り消せない。ゲストはパスワードの代わりにセッションで確認する

削除すると利用者のデータは消えます。auth.exportData()とauth.deleteAccount()は同じ関数です。自分のサーバー(/v1/admin/players/:playerId/export、/v1/admin/players/:playerId/delete)やダッシュボードからも、利用者のデータのエクスポートや削除ができます。

プレイヤーのBAN#

ずるをしたり、ほかの利用者に嫌がらせをしたりする利用者をBANできます。BANすると、その利用者はすべての端末でログアウトされ、ルームの接続もすぐに切れ、ログインと書き込みを断られます。BANはいつでも使えます。オンにする機能はありません。

どこから方法
ダッシュボードプレイヤー → 対象の利用者 → プレイヤーをBAN/BANを解除
AIエージェント(MCP)player_ban/player_unban(毎回あなたに確認します)
自分のサーバーやFunctions(秘密鍵)POST /v1/admin/players/:playerId/ban { reason?, durationHours? } · POST /v1/admin/players/:playerId/unban {}

自分のサーバーやFunctionsからは、durationHours(0より大きく8784=366日まで)を付けると、その時間でBANが自動的に解けます。付けなければ、解除するまで続きます。reason(500文字まで)はあなたのためのメモで、ダッシュボードと監査ログに出ます。応答は{ player: { id, banned, bannedAt, banReason, revokedSessions, banExpiresAt } }です。存在しない利用者や別の環境の利用者は404 player_not_foundになります。公開鍵ではBANできません。

BANは利用者ごとに1つだけです。どこで掛けたBANも、ほかのどこからでも解除できます。ずるを見つけたその場で、自分のコードからBANできます。Functionsのスターターでは、src/kumo.tsのplayers.ban()/players.unban()がこの呼び出しをします(Functions)。

### BANへの異議申し立て

BANした利用者は、BANの見直しを1回だけ求められます(BAN 1件につき1回)。BANへの異議申し立てはBANの一部なので、いつでも使えます。アプリの中では、BANを読んで異議を送ります。

const { sanctions } = await kumo.sanctions.mine();
const ban = sanctions.find((s) => s.kind === 'ban' && s.active);
if (ban && !ban.appeal) await kumo.sanctions.appeal(ban.id, message); // BAN 1件につき1回・2000文字まで

BANされた利用者が自分のBANをkumo.sanctions.mine()(GET /v1/players/me/sanctions)で読めるのは、セッションが残っている間(BANから最大15分)です。異議の画面はそのときに出してください。次に訪れたときでも、SDKは異議を送れます。BANで失効したリフレッシュトークンを残しておき、本人の証明に使います(POST /v1/auth/appeal)。BANのIDが分からないときはkumo.sanctions.appeal(null, message)で送れます。そのトークンを失効させたBANが対象になります。

BANへの異議には、ダッシュボード(プレイヤー → BANの異議申し立て: 受け入れてBANを解く/退ける・短い返事も付けられます)、AIエージェント(appeals_list/appeal_resolve・毎回あなたに確認します)、自分のサーバー(GET /v1/admin/appeals?status=open · POST /v1/admin/appeals/:appealId/resolve { accept, response? })から答えます。答えたあとは、BANの異議がstatus: 'accepted'(BANが解けた)か'rejected'になり、あなたの返事がresponseに入ります。

サーバー側での確認#

自分のサーバー(やFunctions)で利用者が誰かを知る必要がある場合は、ページからアクセストークンを送ってもらい(Authorization: Bearer <token>。await kumo.auth.getAccessToken()で取れます)、サーバーから環境のシークレットキーでKUMODeckに確かめます。

POST /v1/admin/players/verify-token
Authorization: Bearer sk_…
{ "token": "<利用者のアクセストークン>" }
→ { "player": { "id", "displayName", "banned" } }

別の環境のトークンや期限切れのトークンは401 token_expiredです(ページがリフレッシュして再試行します)。bannedは自分で確かめてください。BANされた利用者でも確認は通るので、何をさせるかは自分のコードが決めます。Functionsのスターターではsrc/kumo.tsのplayers.verify()(30秒のキャッシュ付き)がこの呼び出しで、functions-d1 SkillのrequireUser()は401 / 403まで返します(Functions)。シークレットキーはサーバーだけに置き、ページには送らないでください。REST APIを参照してください。