# 認証

> StaticTokenAuth、CallbackAuth、supabaseAuth のいずれかで Nodaro SDK の認証方法を選び、複数のサブドメインで 1 つのブラウザーログインを共有します。

Source: https://nodaro.ai/ja/docs/developers/sdk/auth

**認証プロバイダー**は、どのトークンを送るかを Nodaro SDK に伝えます。クライアントはリクエストのたびに、プロバイダーの `getToken()` を呼び出し、その結果を `Authorization: Bearer <token>` として送信します。プロバイダーが `null` を返した場合、リクエストは、匿名のリクエストとしてヘッダーなしで送られます。

## プロバイダーを選ぶ
| プロバイダー | 用途 | トークンの出どころ |
| --- | --- | --- |
| [`StaticTokenAuth`](#statictokenauth) | サーバーのコード、スクリプト、スケジュールされたジョブ | 固定の API トークンまたは OAuth アクセストークン |
| [`CallbackAuth`](#callbackauth) | 期限があり更新が必要なトークン、独自のセッションストア | あなたの関数（リクエストのたびに呼び出されます） |
| [`supabaseAuth`](#supabaseauthsupabase) | ユーザーが同じ Nodaro インスタンスにログインするブラウザーアプリ | ユーザーの現在のセッション（自動的に更新されます） |
| 独自のオブジェクト | それ以外のすべて | `getToken()` メソッドを持つ任意のオブジェクト |

## 使えるトークン
| トークン | 見た目 | 動作の主体 | 入手先 |
| --- | --- | --- | --- |
| API トークン | `ndr_` の後に 16 進数 64 文字 | あなた | Nodaro の**設定 › APIトークン** |
| OAuth アクセストークン | `ndr_app_` の後に 16 進数 64 文字 | あなたのアプリを承認したユーザー | OAuth のコード交換、[`client.oauth.exchangeCode()`](https://nodaro.ai/docs/developers/sdk/developer-apps) |
| セッショントークン | ログイン中のセッション | ログイン中のユーザー | `supabaseAuth` を通じた Nodaro へのログイン |

- **API トークン**は、利用額の上限がない永続的な認証情報です。無効にするか削除するまで使えるので、サーバーに保管してください。上限とレート設定については、[API 認証](https://nodaro.ai/docs/developers/api/authentication)を参照してください。
- **OAuth アクセストークン**は、ユーザーが承認したスコープだけを持ちます。あなたのアプリがほかの人の代わりに動作する場合に使います。[OAuth](https://nodaro.ai/docs/developers/oauth) を参照してください。
- **セルフホスティングの Community エディションのインストール環境では**、アプリは API トークンを提供しません。代わりにログインし、`supabaseAuth` または `CallbackAuth` でセッショントークンを使ってください。

## Auth インターフェース
```ts
interface Auth {
getToken(): Promise<string | null>
}
```

この形を持つオブジェクトなら何でも、`createClient` の `auth` オプションにできます。以下の 3 つのプロバイダーは、これを実装しています。

## StaticTokenAuth
```ts
new StaticTokenAuth(token: string)
```

1 つの固定トークンをラップします。プロセスの実行中にトークンが変わらない場合に使います。API トークンや、あなたのサーバーが認可コードフローで取得した OAuth アクセストークンなどです。

<TypeTable
type={{
token: {
type: 'string',
required: true,
description: "リクエストのたびに送るトークンです。API トークン（ndr_...）または OAuth アクセストークン（ndr_app_...）です。",
},
}}
/>

```ts

const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new StaticTokenAuth(process.env.NODARO_TOKEN!),
})
```

## CallbackAuth
```ts
new CallbackAuth(fn: () => string | null | Promise<string | null>)
```

リクエストのたびにあなたの関数を呼び出し、その関数が返すトークンを送信します。関数は同期でも非同期でもかまいません。トークンなしでリクエストを送るには、`null` を返します。

<TypeTable
type={{
fn: {
type: '() => string | null | Promise<string | null>',
required: true,
description: "次のリクエスト用のトークンを返すか、匿名のリクエストの場合は null を返します。",
},
}}
/>

トークンの更新、独自のセッションストアの読み取り、認証情報のローテーションに使います。

```ts

const client = createClient({
baseUrl: "https://app.nodaro.ai",
auth: new CallbackAuth(async () => {
const session = await sessionStore.read()
if (!session) return null
if (Date.now() > session.expiresAt - 60_000) {
await refresh(session)
}
return session.accessToken
}),
})
```

## supabaseAuth(supabase)
```ts
supabaseAuth(supabase: SupabaseLikeClient): Auth
```

リクエストのたびに、Supabase v2 クライアントからログイン中のユーザーのトークンを読み取ります。ユーザーが同じ Nodaro インスタンスにログインするブラウザーアプリ、たとえばセルフホスティング環境向けの自作フロントエンドで使います。Nodaro のエディター自体も、同じプロバイダーを使っています。トークンはそのつど読み取られるため、更新されたセッションも自動的に反映されます。

<TypeTable
type={{
supabase: {
type: 'SupabaseLikeClient',
required: true,
description: "Supabase v2 クライアント、または auth.getSession() メソッドが現在のセッションを返す任意のオブジェクトです。それ以外のメソッドは呼び出されません。",
},
}}
/>

```ts

const supabase = createSupabase(
import.meta.env.VITE_SUPABASE_URL,
import.meta.env.VITE_SUPABASE_ANON_KEY,
)

const client = createClient({
baseUrl: import.meta.env.VITE_API_URL ?? "",
auth: supabaseAuth(supabase),
})
```

誰もログインしていない場合、リクエストはトークンなしで送られます。

## createSharedSupabaseClient(options)
```ts

createSharedSupabaseClient<Db = any>(options: {
url: string
anonKey: string
cookieDomain?: string
}): SupabaseClient<Db>
```

セッションをローカルストレージではなく**クッキー**に保存する、ブラウザー用の Supabase クライアントを作成します。`cookieDomain` を指定すると、同じ親ドメインの下にある複数のサブドメインのアプリが、1 つのログインを共有します。いずれか 1 つでログインすると、すべてでログイン状態になり、いずれか 1 つでログアウトすると、すべてでログアウトします。

<TypeTable
type={{
url: {
type: 'string',
required: true,
description: "Nodaro インスタンスの Supabase プロジェクト URL です。",
},
anonKey: {
type: 'string',
required: true,
description: "そのプロジェクトの公開匿名キーです。",
},
cookieDomain: {
type: 'string',
description: ".example.com のような親ドメインを指定すると、そのサブドメイン間でセッションを共有します。省略すると、クッキーは現在のホストにとどまります。",
},
}}
/>

```ts

const supabase = createSharedSupabaseClient({
url: SUPABASE_URL,
anonKey: SUPABASE_ANON_KEY,
cookieDomain: ".example.com",
})

const client = createClient({ baseUrl: "", auth: supabaseAuth(supabase) })
```

- `cookieDomain` が適用されるのは、ページのホストがそのドメインか、そのサブドメインのいずれかである場合だけです。`localhost` やプレビュー URL など、それ以外のホストでは、クッキーは現在のホストにとどまるため、ローカル開発ではオリジンごとに別々のセッションになります。
- 最初の読み込み時に、ローカルストレージにある既存のセッションがクッキーに移され、古いエントリーは削除されます。すでにログインしていたユーザーは、ログイン状態のままになります。期限切れのセッションは破棄されます。
- このエクスポートは、独立したパス `@nodaro/sdk/supabase` にあります。そのため、メインパッケージは Supabase に依存しません。使うには `@supabase/supabase-js` と `@supabase/ssr` をインストールしてください。

## スコープと不足している権限
OAuth アクセストークンは、ユーザーが承認したスコープ（`workflows:read` や `workflows:execute` など）を持ちます。各リソースのリファレンスページには、そのメソッドが必要とするスコープが明記されています。トークンにスコープが不足している場合、メソッドは `ForbiddenError` をスローし、その `missingScope` に不足しているスコープが示されます。

```ts

try {
await client.workflows.run(workflowId)
} catch (err) {
if (err instanceof ForbiddenError && err.missingScope) {
requestConsentFor([err.missingScope]) // send the user through OAuth again
} else {
throw err
}
}
```

API トークンとセッショントークンは、スコープによる制限を受けません。エラークラスの一覧については、[エラー](https://nodaro.ai/docs/developers/sdk/errors)を参照してください。

## ブラウザーのルール
- **API トークンを決してブラウザーに渡さないでください。**ページから誰でも読み取れてしまい、あなたとして動作してしまいます。
- **ブラウザーでの OAuth トークン**は、あなたの開発者アプリの `allowedOrigins` に登録されたオリジンからしか動作しません。[OAuth と開発者アプリ](https://nodaro.ai/docs/developers/sdk/developer-apps)を参照してください。
- **`supabaseAuth` によるセッショントークン**は、そのリストと照合されません。
- **シークレットはサーバーに置いてください。**OAuth のコード交換にはクライアントシークレットが必要なので、[`client.oauth.exchangeCode()`](https://nodaro.ai/docs/developers/sdk/developer-apps) はサーバーのコードでだけ実行してください。

## Frequently asked questions

### サーバーはどの認証プロバイダーを使うべきですか？

StaticTokenAuth です。環境変数から読み取った、ndr_ で始まる API トークンか、ndr_app_ で始まる OAuth アクセストークンを渡します。

### SDK で、期限が近づいたトークンを更新するにはどうすればよいですか？

CallbackAuth を使います。SDK はリクエストのたびにあなたの関数を呼び出すので、その関数で期限を確認し、トークンを更新して、新しいトークンを返せます。

### API トークンをブラウザーアプリに置いてもよいですか？

いけません。API トークンはあなたとして動作し、利用額の上限もありません。しかも、ブラウザーのコードからは誰でも読み取れてしまいます。ブラウザーでは、ユーザーをログインさせてそのセッションを使うか、OAuth を使ってください。

### ForbiddenError.missingScope は何を意味しますか？

OAuth トークンは有効ですが、そのエンドポイントが必要とするスコープ（たとえば workflows:execute）が付与されていません。ユーザーにそのスコープの許可を求め、新しいトークンで再試行してください。
