---
title: 認可の流れ（OAuth）
description: 認可コードフローとPKCEで、店舗の許可を得てアクセストークンを受け取る手順です。
---

外部連携APIは、OAuth 2.1 の認可コードフローで許可を得ます。PKCE（S256）は必須です。トークンを受け取る処理は、クライアントシークレットを守れるサーバー側で行ってください。

## エンドポイント

| 用途                     | URL                                                                            |
| ------------------------ | ------------------------------------------------------------------------------ |
| 認可                     | `https://api.toreca-cloud.com/api/auth/oauth2/authorize`                       |
| トークン                 | `https://api.toreca-cloud.com/api/auth/oauth2/token`                           |
| 失効                     | `https://api.toreca-cloud.com/api/auth/oauth2/revoke`                          |
| リソース（`resource`）   | `https://api.toreca-cloud.com/partner/v1`                                      |
| 認可サーバーのメタデータ | `https://api.toreca-cloud.com/api/auth/.well-known/oauth-authorization-server` |
| 保護リソースのメタデータ | `https://api.toreca-cloud.com/.well-known/oauth-protected-resource/partner/v1` |

認可・トークン・更新のすべてのリクエストに、`resource=https://api.toreca-cloud.com/partner/v1` を付けます。

## 1. 認可画面を開く

`code_verifier` を乱数で作り、そのSHA-256を `code_challenge` にします。`state` も乱数で作り、戻ってきたときに照合します。

```ts
import { createHash, randomBytes } from "node:crypto";

const verifier = randomBytes(32).toString("base64url");
const challenge = createHash("sha256").update(verifier).digest("base64url");
const state = randomBytes(16).toString("hex");

const url = new URL("https://api.toreca-cloud.com/api/auth/oauth2/authorize");
url.search = new URLSearchParams({
  response_type: "code",
  client_id: process.env.TORECA_CLIENT_ID!,
  redirect_uri: "https://lp.example.com/oauth/callback",
  scope: "openid profile offline_access purchase-boosts:read",
  state,
  code_challenge: challenge,
  code_challenge_method: "S256",
  resource: "https://api.toreca-cloud.com/partner/v1",
}).toString();
// verifier と state をセッションに保存してから、url へリダイレクトする
```

`scope` には `offline_access` を入れてください。入れないとリフレッシュトークンが発行されず、15分ごとに許可を取り直すことになります。登録時に許可されていない権限を求めると、`invalid_scope` で拒否されます。

## 2. 店舗のメンバーが許可する

メンバーはトレカクラウドにログインし、接続する店舗と許可する内容を確かめて「許可する」を押します。許可すると、`redirect_uri` に `code` と `state` が付いて戻ります。

## 3. コードをトークンに交換する

```ts
const response = await fetch(
  "https://api.toreca-cloud.com/api/auth/oauth2/token",
  {
    method: "POST",
    headers: {
      Authorization:
        "Basic " +
        Buffer.from(
          `${encodeURIComponent(clientId)}:${encodeURIComponent(clientSecret)}`,
        ).toString("base64"),
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code,
      redirect_uri: "https://lp.example.com/oauth/callback",
      code_verifier: verifier,
      resource: "https://api.toreca-cloud.com/partner/v1",
    }),
  },
);
const token = await response.json();
// { access_token, refresh_token, token_type: "Bearer", expires_in: 900, scope }
```

## 4. 接続を作る

トークンを受け取ったら、最初に `POST /partner/v1/connection` を呼びます。応答には接続した店舗の情報が入ります。

```bash
curl -X POST https://api.toreca-cloud.com/partner/v1/connection \
  -H "Authorization: Bearer $ACCESS_TOKEN"
```

以降のAPIは、この接続が有効な間だけ使えます。トークンの保管と更新は[トークンの保管と有効期限](/api/tokens)で説明します。
