# OAuth アプリ

> OAuth アプリを登録し、ユーザーを Nodaro の同意画面に送り、コードを 90 日間有効なアクセストークンと交換して、各ユーザーの代わりに API を呼び出します。

Source: https://nodaro.ai/ja/docs/developers/oauth

**OAuth アプリ**を使うと、あなたのプロダクトが、ほかの Nodaro ユーザーの代わりに Nodaro API を呼び出せます。各ユーザーは同意画面であなたのアプリを承認し、あなたのサーバーはその結果得られるコードをアクセストークンと交換します。そのトークンによる呼び出しは、そのユーザーとして、付与されたスコープの範囲内で行われます。Nodaro は、標準の OAuth 2.0 認可コードフローを実装しており、シークレットを保持できないクライアントには PKCE を使います。

開発者アプリは、Nodaro Cloud と Business エディションのインストール環境で利用できます。

## OAuth か個人用 API トークンか
| 作っているもの | 使うもの | トークンの形式 |
| --- | --- | --- |
| 自分のアカウントを使う、スクリプト、cron ジョブ、CI ジョブ、バックエンド | 個人用 API トークン | `ndr_` の後に 16 進数 64 文字 |
| ユーザーがそれぞれ自分の Nodaro アカウントを持つ、ホスティング型のプロダクト | OAuth アプリ | `ndr_app_` の後に 16 進数 64 文字 |

自分のアカウントだけで認証し、同意画面もユーザーごとの取り消しも不要な場合は、**個人用 API トークン**を使います。[認証](https://nodaro.ai/docs/developers/api/authentication)を参照してください。

次のような場合は、**OAuth** を使います。

- Web アプリ、SaaS、マーケットプレイスなど、ホスティング型のプロダクトを構築していて、ユーザーがそれぞれ自分の Nodaro アカウントを持っている。
- 各ユーザーが、自分のアカウントでできることの一部だけを、あなたのアプリに許可する。
- 各ユーザーが、ほかのアプリに影響を与えずに、いつでもあなたのアプリのアクセスを取り消せる。

## フローの仕組み
1. ユーザーが、あなたのサイトで **Nodaro に接続**をクリックします。
2. あなたのサイトが、`client_id`、`redirect_uri`、`scope`、`state` を付けて、ブラウザーを Nodaro の `/oauth/authorize` ページに送ります。
3. Nodaro は、必要であればログインの後に、同意画面を表示します。この画面には、あなたのアプリの名前、ロゴ、要求するスコープ、そしてアクセスを許可することになるアカウントが表示されます。**別のアカウントを使用**をクリックすると、ユーザーはログアウトし、同じ画面に戻ります。
4. ユーザーが**許可**をクリックすると、Nodaro は 1 回限りの認可コードを作成します。
5. ブラウザーが、`?code=...&state=...` を付けて、あなたの `redirect_uri` に戻ります。
6. あなたのサーバーが、`client_id` と `client_secret` を使って、`POST /v1/oauth/token` でコードをアクセストークンと交換します。
7. あなたのサーバーが、ユーザーの代わりに、`Authorization: Bearer ndr_app_...` を付けて Nodaro API を呼び出します。

## アプリを登録する
### 開発者アプリを開く
対象の Nodaro インスタンスで、**設定 › 開発者アプリ**（`/settings/developer-apps`）を開き、**アプリを作成**をクリックします。

### フォームに入力する
名前、リダイレクト URI、スコープを入力します。各フィールドは、下の表で説明します。

### クライアントシークレットを保存する
**アプリを作成しました**ダイアログには、`app_` で始まる**クライアント ID** と、`sec_` で始まる**クライアントシークレット**が表示されます。シークレットが表示されるのは、これ 1 回だけです。ダイアログを閉じる前に、シークレットマネージャーにコピーしてください。Nodaro はそのハッシュしか保存しないため、二度と表示できません。

| フィールド | 必須 | ルール |
| --- | --- | --- |
| **名前** | はい | 1〜100 文字です。同意画面に表示されます。 |
| **説明** | いいえ | 最大 500 文字です。同意画面で、名前の下に表示されます。 |
| **リダイレクト URI** | はい | 1 行に 1 つ、1〜10 個です。それぞれ `https://` のアドレス、または開発用の `http://localhost` のアドレスです。Nodaro はバイト単位で比較し、ワイルドカードには対応していません。 |
| **許可するオリジン** | いいえ | パス、クエリ、フラグメントを含まない、素のオリジンを最大 5 個までです。フロントエンドがブラウザーから Nodaro を呼び出す場合（CORS）にだけ必要です。 |
| **要求するスコープ** | はい | 少なくとも 1 つです。これは、あなたのアプリが要求できる上限です。ユーザーはこれより少なく許可でき、あなたのアプリはこれより多くを要求することはできません。 |
| **ホームページの URL**、**ロゴの URL** | いいえ | 作成後に、アプリのページで設定します。それぞれ `https://` または `http://localhost` のアドレスです。正方形のロゴが最も見栄えがよくなります。 |

### アプリを管理する
- シークレットを紛失したときや、日常的なセキュリティ対策として、アプリのページで**シークレットを再発行**します。古いシークレットはその時点で使えなくなるため、稼働中のすべてのサービスの設定を、すぐに更新してください。
- **アプリを削除**すると、そのアプリに発行されたすべてのアクセストークンが取り消されます。承認していたユーザーは、もう一度接続する必要があります。
- **アプリは 1 ユーザーにつき 5 個までです。**自己登録した MCP クライアントは同じ一覧に表示されますが、この上限には含まれません。
- **コードからは**、SDK の `client.developerApps` が、アプリの作成、更新、削除、シークレットの再発行を行います。[SDK](https://nodaro.ai/docs/developers/sdk) を参照してください。

## スコープ
スコープは、あなたのアプリが要求する 1 つの権限です。実際に使うスコープだけを要求してください。ユーザーは、要求されたすべてのスコープを同意画面で目にするため、短いリストのほうが信頼を得やすくなります。

| スコープ | 同意画面での表示 | 許可される操作 |
| --- | --- | --- |
| `workflows:read` | ワークフローを読み取る | ワークフローの一覧表示、読み取り、エクスポートです。 |
| `workflows:write` | ワークフローを作成・変更する | ワークフローの作成、更新、削除、インポート、移動、サブワークフローの作成です。 |
| `workflows:execute` | ユーザーに代わってワークフローを実行する | ワークフローと公開アプリの実行、MCP を通じた単一の生成ノードの実行、プロンプトウィザードの使用です。 |
| `jobs:read` | ジョブのステータスと結果を読み取る | ジョブと、そのステータスと結果の読み取りです。 |
| `assets:read` | アップロードしたアセットを読み取る | ギャラリー、アップロード、お気に入り、アプリの実行、キャラクター、ロケーション、オブジェクト、クリーチャーの読み取りです。 |
| `assets:write` | アカウントにアセットをアップロードする | メディアのアップロード、アセットのお気に入り登録、キャラクター、ロケーション、オブジェクトの作成と更新です。 |
| `credits:read` | クレジット残高を確認する | クレジット残高とクレジットの取引履歴の読み取りです。 |
| `apps:read` | 公開済みのアプリを読み取る | 公開アプリの一覧表示と、その入力の読み取りです。 |
| `pipelines:read` | パイプラインを読み取る | ストーリーから動画へのパイプラインと、そのステータス、保留中の承認の読み取りです。 |
| `pipelines:execute` | ユーザーに代わってパイプラインを実行する（クレジットが消費される場合があります） | パイプラインの開始、各ステージの実行、ステージからの分岐です。 |
| `pipelines:approve` | ユーザーに代わってパイプラインのステージを承認する | ステージの出力の承認、ステージのチャットとシーン用ヘルパーの使用です。 |
| `presets:read` | 保存済みのプリセットを読み取る | ユーザーのノードプリセットとお気に入りのプリセットの読み取りです。 |
| `workspaces:read` | 所属しているワークスペースを確認する | ユーザーのワークスペースの一覧表示です。 |
| `workspaces:write` | 作業先のワークスペースを選択する | アプリが動作するワークスペースの選択です。 |

- 一部のスコープは REST のルートを制御し、一部は [MCP ツール](https://nodaro.ai/docs/mcp/tools)を制御し、両方を制御するものもあります。MCP サーバーは、トークンにないスコープを必要とするツールを、すべて非表示にします。
- ルートが必要とするスコープを持たないトークンには `403 insufficient_scope` が返され、不足しているスコープは `missingScope` に示されます。[エラー](#errors)を参照してください。
- ワークスペースのスコープは、組織機能が導入される前に発行されたトークンには、決して追加されません。これらを付与するには、ユーザーにもう一度あなたのアプリを承認してもらう必要があります。
- 公開アプリの実行には、開始のために `workflows:execute` が、進行状況の読み取りのために `jobs:read` が必要です。

## ユーザーを同意画面に送る
ユーザーが **Nodaro に接続**をクリックしたら、ブラウザーを次の URL に送ります。

```text
https://nodaro.example.com/oauth/authorize?
client_id=app_...&
redirect_uri=https://yourapp.com/oauth/callback&
response_type=code&
scope=workflows:read+workflows:execute&
state=<random CSRF token>
```

| パラメーター | ルール |
| --- | --- |
| `client_id` | あなたのアプリのクライアント ID です。 |
| `redirect_uri` | 登録済みのリダイレクト URI のいずれか 1 つと、バイト単位で完全に一致する必要があります。一致しない場合は `400 invalid_redirect_uri` で拒否されます。 |
| `response_type` | 常に `code` です。同意画面は、それ以外の値を拒否します。 |
| `scope` | 要求するスコープを、スペースまたは `+` で区切って指定します。アプリの要求するスコープの部分集合である必要があります。 |
| `state` | 認可のたびに作成し、ユーザーのセッションに保持する、ランダムなトークンです。Nodaro はこれをそのまま返すので、必ず確認してください。 |

あなたのサーバーで `state` を作成します。

```ts

// In your /connect handler:
const state = randomBytes(32).toString("hex")
req.session.oauthState = state

const url = new URL("https://nodaro.example.com/oauth/authorize")
url.searchParams.set("client_id", process.env.NODARO_CLIENT_ID!)
url.searchParams.set("redirect_uri", "https://yourapp.com/oauth/callback")
url.searchParams.set("response_type", "code")
url.searchParams.set("scope", "workflows:read workflows:execute")
url.searchParams.set("state", state)
res.redirect(url.toString())
```

- **ユーザーがキャンセルをクリックすると、**Nodaro は、`error=access_denied`、`error_description`、あなたの `state` を付けて、あなたの `redirect_uri` にリダイレクトします。これは異常ではなく、通常の結果として扱ってください。
- **リダイレクト URI が登録されていない場合、**同意画面はエラーページを表示し、許可の場合もキャンセルの場合も、どこにもリダイレクトしません。

## コードをトークンと交換する
ユーザーが**許可**をクリックすると、Nodaro はブラウザーをあなたのコールバックに送ります。

```text
https://yourapp.com/oauth/callback?code=ndr_code_...&state=<your state>
```

1. **まず `state` を確認します。**ユーザーのセッションにある値と一致しない場合は、処理を止めてください。これは、クロスサイトリクエストフォージェリに対する防御です。
2. **コードの交換は、あなたのサーバーで行います。**この呼び出しをブラウザーから行わないでください。開発者ツールを使えば、誰でもあなたの `client_secret` を読み取れてしまいます。

**TypeScript SDK**

```ts

// The token endpoint is public: your client ID and secret authenticate
// the request, so the client needs no token of its own.
const client = createClient({
baseUrl: "https://nodaro.example.com",
auth: new StaticTokenAuth(""),
})

const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "https://yourapp.com/oauth/callback",
})
// tokens.access_token: "ndr_app_..."
// tokens.scope:        the scopes the user granted, separated by spaces
// tokens.expires_in:   7776000 (seconds, that is 90 days)
// tokens.token_type:   "Bearer"
```

**curl**

```bash
curl -X POST https://nodaro.example.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
"grant_type": "authorization_code",
"client_id": "app_...",
"client_secret": "sec_...",
"code": "ndr_code_...",
"redirect_uri": "https://yourapp.com/oauth/callback"
}'
```

レスポンスは、標準の OAuth のフィールド名を使います。

```json
{
"access_token": "ndr_app_...",
"token_type": "Bearer",
"scope": "workflows:read workflows:execute",
"expires_in": 7776000
}
```

- **コードは 1 回だけ使えます。**同じコードを 2 回目に交換しようとすると、`400 invalid_grant` が返ります。
- **コードは、発行から 10 分で期限切れになります。**コールバックで受け取ったら、すぐに交換してください。
- **トークンエンドポイントは、JSON とフォームエンコードのボディの両方を受け付けます。**`application/x-www-form-urlencoded` で POST する標準の OAuth クライアントも、`client_secret_post` 方式のまま、変更なく動作します。

## パブリッククライアント：PKCE
モバイルアプリ、シングルページアプリ、CLI ツールは、`client_secret` を保持できません。これらは代わりに PKCE を使います。Nodaro が対応しているのは `S256` 方式だけで、`plain` は `400 invalid_request` で拒否されます。

### ベリファイアとチャレンジを作成する
リダイレクトの前に、ランダムで高エントロピーな `code_verifier` を作成します。`code_challenge` は、そのベリファイアの SHA-256 ハッシュを base64url エンコードして導出します。

### 認可リクエストにチャレンジを付けて送る
認可 URL に `code_challenge` と `code_challenge_method=S256` を追加します。

```text
https://nodaro.example.com/oauth/authorize?
client_id=app_...&
redirect_uri=https://yourapp.com/oauth/callback&
response_type=code&
scope=workflows:read+workflows:execute&
state=<random CSRF token>&
code_challenge=<base64url SHA-256 of the verifier>&
code_challenge_method=S256
```

### トークンの交換にベリファイアを付けて送る
`client_secret` の代わりに `code_verifier` を送ります。

```bash
curl -X POST https://nodaro.example.com/v1/oauth/token \
  -H "Content-Type: application/json" \
  -d '{
"grant_type": "authorization_code",
"client_id": "app_...",
"code": "ndr_code_...",
"redirect_uri": "https://yourapp.com/oauth/callback",
"code_verifier": "<the original verifier>"
}'
```

コンフィデンシャルクライアントは、シークレットと PKCE のベリファイアの両方を送ってもかまいません。Nodaro は、送られたものをそれぞれ検証します。

## トークンで API を呼び出す
ユーザーごとに、そのユーザーのアクセストークンを使ったクライアントを 1 つ作成します。

```ts

const userClient = createClient({
baseUrl: "https://nodaro.example.com",
auth: new StaticTokenAuth(tokens.access_token),
})

// Every call acts as the user who authorized your app, within the granted scopes.
const projects = await userClient.projects.list()
const workflows = await userClient.workflows.list(projects.data[0].id)
const run = await userClient.workflows.run(workflows.data[0].id)
```

SDK を使わない場合は、各 REST 呼び出しの `Authorization: Bearer` ヘッダーにトークンを送ってください。エンドポイントは、[REST API](https://nodaro.ai/docs/developers/api) のリファレンスにあります。

## トークンの期限が切れたとき
アクセストークンの有効期間は **90 日**です。Nodaro は**リフレッシュトークンを発行しません**。トークンの種類は 1 つ、保存する場所は 1 つ、期限のルールも 1 つで、その代わりに 90 日ごとに同意画面を通ることになります。

- トークンの期限が切れるか取り消されると、API の呼び出しは `401` を返します。ユーザーをもう一度 `/oauth/authorize` に送ってください。
- ユーザーがあなたのアプリをもう一度承認すると、Nodaro はその認可を更新し、新しいトークンを発行します。以前のトークンは、期限が切れるか取り消されるまで有効なままなので、どのトークンが使われているかを把握しておいてください。
- スコープを追加するには、より広い `scope` を付けた認可 URL に、ユーザーを通してください。既存の認可が広がります。

## トークンを安全に保管する
- **トークンはサーバーに置きます。**アクセストークンを、`localStorage`、`sessionStorage`、JavaScript が読み取れるクッキーに、決して入れないでください。
- プラットフォームが対応していれば、**トークンを保存時に暗号化します。**Nodaro は各トークンの SHA-256 ハッシュしか保持していないため、トークンが漏れる経路は、あなた自身のデータベースの漏えいだけです。
- **トークンをユーザー間で共有しないでください。**各トークンは 1 人の Nodaro ユーザーに属します。別のユーザーのコンテキストで使うのは、認可のバグです。

## トークンを取り消す
ユーザーがあなたのアプリからログアウトしたとき、あなたのプラットフォームでアカウントを削除したとき、またはあなたのアプリの設定で **Nodaro との接続を解除**をクリックしたときに、トークンを取り消します。

**TypeScript SDK**

```ts
await client.oauth.revoke(tokens.access_token)
// { success: true }
```

**curl**

```bash
curl -X POST https://nodaro.example.com/v1/oauth/revoke \
  -H "Content-Type: application/json" \
  -d '{ "token": "ndr_app_..." }'
```

取り消しのエンドポイントは、存在しないトークンに対してでも、常に `200` を返します。これにより、推測したトークンが有効かどうかを、このエンドポイントで確かめることはできません。取り消しの後は、そのトークンでの呼び出しは `401` を返します。

### ユーザーがアクセスを取り消したとき
ユーザーが自分で、あなたのアプリのアクセスを終わらせることもできます。**設定 › 接続済みアプリ**（`/settings/connected-apps`）には、ユーザーのアカウントにアクセスできるすべてのアプリと AI アシスタントが一覧表示されます。各項目には、名前と種類、接続した日、最後に使われた日、持っているスコープが表示されます。**アクセスを取り消す**は、確認を求めた後、その認可と、その認可のもとで発行されたすべてのトークンを、すぐに無効にします。

その後、あなたのトークンでの呼び出しは `401` を返します。期限切れのトークンと同じように扱い、ユーザーにもう一度同意画面を通ってもらってください。

これらの認可を一覧表示したり取り消したりできるのは、ログイン中のブラウザーセッションだけです。OAuth アクセストークンや個人用 API トークンでは、`GET /v1/me/connected-apps` と `POST /v1/me/connected-apps/:id/revoke` から `401` が返されるため、アプリがこの一覧を読み取ったり変更したりすることはできません。アプリ自身のアクセスをコードから終わらせるには、上記の取り消しのエンドポイントを使ってください。

## エラー
トークンエンドポイントは、標準の OAuth のエラーで応答します。

| ステータス | エラー | 発生する状況 | 対処 |
| --- | --- | --- | --- |
| `400` | `invalid_request` | リクエストの形式が正しくないか、フィールドが不足しています。 | リクエストを修正してください。 |
| `401` | `invalid_client`（Unknown client） | `client_id` が、登録されているどのアプリとも一致しません。 | 設定のクライアント ID を確認してください。 |
| `401` | `invalid_client`（Bad client_secret） | シークレットが正しくありません。多くは、再発行後の古いシークレットです。 | 現在のシークレットを読み込み、サービスを再起動してください。 |
| `400` | `invalid_grant` | コードの発行から 10 分を超えている、すでに使われている、または `redirect_uri` が認可リクエストのものと異なります。 | 同じ URI を使って、ユーザーにもう一度同意画面を通ってもらってください。 |

認可のステップは、これらのエラーについては同意画面で応答し、リダイレクトは行いません。

| ステータス | コード | 発生する状況 | 対処 |
| --- | --- | --- | --- |
| `400` | `invalid_redirect_uri` | `redirect_uri` が、そのアプリに登録されていません。 | アプリのページで URI を追加してください。 |
| `400` | `invalid_scope` | 要求されたスコープが、アプリの要求するスコープに含まれていません。 | 先にそのスコープをアプリに追加してください。 |
| `404` | `invalid_client` | `client_id` が不明である、またはアプリが停止されています。 | クライアント ID とアプリの状態を確認してください。 |

アクセストークンを使った API の呼び出しは、次のように失敗することがあります。

| ステータス | コード | 発生する状況 | 対処 |
| --- | --- | --- | --- |
| `401` | `unauthorized` | トークンの期限が切れている、取り消されている、または形式が正しくありません。 | ユーザーにもう一度同意画面を通ってもらってください。 |
| `403` | `insufficient_scope` | トークンに、ルートが必要とするスコープがありません。ボディの `missingScope` にそのスコープが示されます。 | より広いスコープで、ユーザーに同意画面を通ってもらってください。 |

SDK は、この `403` のケースを、型付きの `missingScope` を持つ `ForbiddenError` としてスローするため、ワンクリックでの再同意を提供できます。

```ts

try {
await userClient.workflows.run(workflowId)
} catch (err) {
if (err instanceof ForbiddenError && err.missingScope) {
// Send the user back to /oauth/authorize with the broader scope list.
redirectToConsent({ scopes: [...currentScopes, err.missingScope] })
return
}
throw err
}
```

## セキュリティのチェックリスト
- **どこでも HTTPS。**Nodaro のインスタンス、あなたのアプリ、すべてのリダイレクト URI は `https://` を使います。`http://localhost` は、ローカルでの開発専用です。
- **すべてのコールバックで `state` を確認します。**認可ごとに作成し、ユーザーのセッションに保持してください。
- **`client_secret` はサーバーに置きます。**ブラウザーアプリに同梱したり、ログに出力したり、エラーメッセージにそのまま含めたりしないでください。
- **シークレットを再発行します。**少なくとも年に 1 回、そして漏えいの疑いがあれば直ちに行ってください。
- **自分のリダイレクト URI だけを登録します。**実際に使うアドレスだけを登録してください。
- **最小限のスコープだけを要求します。**そのスコープが必要な機能を作るときに、スコープを追加してください。
- **ログアウト時に取り消します。**ユーザーがあなたのアプリからログアウトしたら、取り消しのエンドポイントを呼び出し、トークンが再利用されないようにしてください。
- **`missingScope` を処理します。**汎用的な「アクセスが拒否されました」ページではなく、ユーザーに再同意を提供してください。

## フローをローカルでテストする
最も手早くテストするには、Nodaro Cloud や Business エディションのインストール環境のように、**開発者アプリ**を持つ Nodaro インスタンスと、自分のマシン上の小さなコールバックサーバーが必要です。

### テスト用のアプリを登録する
リダイレクト URI が `http://localhost:8080/cb` で、スコープが `workflows:read`、`workflows:execute`、`jobs:read` のアプリを作成します。

### ポート 8080 でコールバックサーバーを実行する
```ts

const NODARO_URL = "https://app.nodaro.ai" // or your Business edition install

const app = express()

app.get("/cb", async (req, res) => {
const client = createClient({ baseUrl: NODARO_URL, auth: new StaticTokenAuth("") })
const tokens = await client.oauth.exchangeCode({
client_id: process.env.NODARO_CLIENT_ID!,
client_secret: process.env.NODARO_CLIENT_SECRET!,
code: req.query.code as string,
redirect_uri: "http://localhost:8080/cb",
})
res.json(tokens)
})

app.listen(8080)
```

### 認可 URL を開いて許可をクリックする
インスタンス上で `/oauth/authorize?client_id=app_...&redirect_uri=http://localhost:8080/cb&response_type=code&scope=workflows:read+workflows:execute&state=test123` を開きます。**許可**をクリックすると、コールバックサーバーがトークンの JSON を表示します。

### 実際のルートを呼び出す
`GET /v1/projects/<id>/workflows` のようなルートで、そのトークンを使い、データとともに `200` が返ることを確認します。

## MCP クライアント向けのディスカバリーと動的登録
Claude、ChatGPT、Cursor などの MCP クライアントは、自分で OAuth のエンドポイントを見つけ、人が開発者アプリのページを開かなくても、実行時に自身を登録できます。ユーザー側の手順は、[クライアントを接続する](https://nodaro.ai/docs/mcp/connect)を参照してください。

### ディスカバリードキュメント
| エンドポイント | 標準 | 目的 |
| --- | --- | --- |
| `GET /.well-known/oauth-authorization-server` | RFC 8414 | 認可、トークンの取得、登録、取り消しの場所です。 |
| `GET /.well-known/oauth-protected-resource` | RFC 9728 | MCP のリソース `https://mcp.nodaro.ai/mcp` を、その認可サーバーに結び付けます。 |

各ドキュメントは、末尾に `/mcp` を付けた形式でも提供されます。一部の厳格なクライアントが、その形式を最初に試すためです。この 4 つはすべて、Nodaro のホストと MCP のホストのどちらからでも取得できます。発行者は、インスタンスの `PUBLIC_URL` で、Nodaro Cloud では `https://app.nodaro.ai` です。

認可サーバーのメタデータは、次を示します。

- 認可、トークン、登録、取り消しの各エンドポイント
- レスポンスタイプ `code` とグラントタイプ `authorization_code`
- `S256` 方式だけの PKCE
- トークンエンドポイントでの `client_secret_post` 認証方式
- `scopes_supported` に含まれるすべてのスコープ

### 動的クライアント登録
クライアントは、`POST /v1/oauth/register`（RFC 7591）で自身を登録し、`ndr_dcr_` で始まる `client_id` と `client_secret` を受け取ります。このエンドポイントが受け付けるのは、1 つの IP アドレスから 1 分あたり 10 リクエストまでです。インスタンスの運用者は、`MCP_DYNAMIC_REGISTRATION` を設定します。

| モード | 動作 |
| --- | --- |
| `allowlist`（デフォルト） | `MCP_DCR_ALLOWLIST` にあるクライアント名だけが登録できます。それ以外は `403 client_not_allowed` になります。 |
| `open` | どのクライアントも登録できます。クライアント名とリダイレクト URI の組み合わせごとに、24 時間で未使用のまま保持できる登録は最大 5 件です（`429 too_many_open_registrations`）。 |
| `off` | 登録が無効になります（`403 dcr_disabled`）。運用者が、代わりに固定のクライアント ID とシークレットを配布します。 |

自己登録したクライアントは、自分で名前を決めているため、同意画面では、Nodaro がその名前を検証していないことがユーザーに警告されます。宣言するスコープは参考情報にすぎません。アクセスを実際に決めるのはユーザーの同意であり、クライアントはどの有効なスコープでも要求できます。運用者向けの設定は、[セルフホスティング環境での MCP](https://nodaro.ai/docs/self-hosting/mcp) を参照してください。

## セルフホスティング環境での Figma 用プラグイン
Nodaro の Figma 用プラグインは Figma の中で動作するため、リダイレクトを受け取れません。代わりに、デバイス方式のハンドシェイクで接続します。プラグインがユーザーに短いコードを表示し、ユーザーは通常の同意画面でプラグインを承認してそのコードを入力し、プラグインがトークンを受け取ります。このトークンは、スコープ `jobs:read`、`assets:read`、`assets:write`、`credits:read` を持つ、ごく普通の開発者アプリのトークンであり、ほかのトークンと同じように取り消せます。

自分のインストール環境にプラグインを接続させるには、次のようにします。

1. **開発者アプリ**で、または `POST /v1/developer-apps` で、開発者アプリを登録します。そのリダイレクト URI に `<PUBLIC_URL>/v1/oauth/plugin/callback` を追加し、上記の 4 つのスコープを要求します。
2. サーバーの `FIGMA_PLUGIN_OAUTH_CLIENT_ID` に、そのアプリのクライアント ID を設定します。

この設定がない場合、プラグインの接続用ルートはすべて `503 plugin_connect_not_configured` を返します。アプリにコールバック URI やスコープが不足している場合、ルートは `503 plugin_connect_misconfigured` を返し、サーバーのログに、何が不足しているかが記録されます。

## Frequently asked questions

### 個人用 API トークンではなく OAuth が必要なのは、どんなときですか？

プロダクトがほかの人の Nodaro アカウントに対して動作し、各ユーザーがあなたのアプリを承認し、そのアクセスを取り消せる必要がある場合は、OAuth を使います。自分のアカウントだけを使うスクリプトやサーバーには、個人用 API トークンのほうが簡単です。

### Nodaro の OAuth アクセストークンの有効期間は、どれくらいですか？

90 日です。Nodaro はリフレッシュトークンを発行しないため、トークンの期限が切れるか取り消されると、API の呼び出しは 401 を返し、ユーザーにもう一度同意画面を通ってもらうことになります。

### Nodaro は PKCE に対応していますか？

はい、S256 方式だけに対応しています。モバイルアプリ、シングルページアプリ、CLI ツールは、クライアントシークレットの代わりに、認可リクエストで code_challenge を、トークンの交換でそれに対応する code_verifier を送ります。

### クライアントシークレットを紛失した場合、どうなりますか？

Nodaro はシークレットのハッシュしか保存していないため、二度と表示できません。アプリのページでシークレットを再発行すると、新しいものが手に入ります。古いシークレットは、その時点で使えなくなります。

### 開発者アプリは、いくつ登録できますか？

1 ユーザーにつき 5 個です。自己登録した MCP クライアントは同じ一覧に表示されますが、この上限には含まれません。

### ユーザーは、私のアプリに許可したアクセスを取り消せますか？

はい。ユーザーは「設定 › 接続済みアプリ」で、自分のアカウントにアクセスできるアプリであれば、どのアプリのアクセスでも取り消せます。その認可と、その認可のもとで発行されたすべてのトークンはすぐに使えなくなり、あなたの呼び出しは 401 を返します。ユーザーにもう一度同意画面を通ってもらってください。
