> ## Documentation Index
> Fetch the complete documentation index at: https://developers.authlete.com/llms.txt
> Use this file to discover all available pages before exploring further.

# OAuth 2.0 Basics

> OAuth 2.0 認可サーバーが Authlete API を活用する際の、しくみを理解するためのチュートリアルです。

export const _ReflectCell = ({id}) => {
  const [value, setValue] = useState(' ');
  const update = useCallback(() => {
    if (typeof document === 'undefined') return;
    const el = document.getElementById(id);
    setValue((el ? el.value || el.placeholder || '' : '').trim() || ' ');
  }, [id]);
  useEffect(() => {
    update();
    const el = document.getElementById(id);
    if (el) {
      el.addEventListener('input', update);
      return () => el.removeEventListener('input', update);
    }
    const iv = setInterval(update, 300);
    return () => clearInterval(iv);
  }, [update]);
  return <code className="text-zinc-800 dark:text-zinc-200">{value}</code>;
};

export const TextReflect = ({id}) => <_ReflectCell id={id} />;

export const InputTable = ({rows, col1 = '項目', col2 = '値'}) => <div className="rounded-xl overflow-hidden border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900/50 my-4 shadow-sm">
    <table className="w-full text-sm">
      <thead>
        <tr className="border-b border-zinc-200 dark:border-zinc-700 bg-zinc-100/80 dark:bg-zinc-800/50">
          <th className="text-left font-medium text-zinc-700 dark:text-zinc-300 px-4 py-3 whitespace-nowrap">{col1}</th>
          <th className="text-left font-medium text-zinc-700 dark:text-zinc-300 px-4 py-3">{col2}</th>
        </tr>
      </thead>
      <tbody className="divide-y divide-zinc-200 dark:divide-zinc-700">
        {rows.map((row, i) => <tr key={i}>
            <td className="px-4 py-3 text-zinc-600 dark:text-zinc-400 whitespace-nowrap">
              {row.code ? <><code className="text-zinc-800 dark:text-zinc-200">{row.code}</code>{row.label ? ' ' + row.label : ''}</> : row.label}
            </td>
            <td className="px-4 py-3">
              {('static' in row) ? <code className="text-zinc-800 dark:text-zinc-200">{row.static}</code> : ('reflect' in row) ? <_ReflectCell id={row.reflect} /> : <input type="text" id={row.id} defaultValue={row.defaultValue} placeholder={row.placeholder} className="w-full max-w-md px-3 py-2 rounded-md border border-zinc-300 dark:border-zinc-600 bg-white dark:bg-zinc-800 text-zinc-900 dark:text-zinc-100 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:focus:ring-blue-400 outline-none placeholder-zinc-400 dark:placeholder-zinc-500" />}
            </td>
          </tr>)}
      </tbody>
    </table>
  </div>;

export const DynamicCurl = ({children, inputIds = [], language = 'bash'}) => {
  const template = typeof children === 'string' ? children : '';
  const [output, setOutput] = useState('');
  const [copied, setCopied] = useState(false);
  const update = useCallback(() => {
    if (typeof document === 'undefined') return;
    let s = template;
    inputIds.forEach((id, i) => {
      const el = document.getElementById(id);
      const v = el && el.value ? el.value : el ? el.placeholder || '' : '';
      s = s.split('$' + (i + 1)).join(v);
    });
    setOutput(s.trim());
  }, [template, inputIds]);
  useEffect(() => {
    update();
    const iv = setInterval(update, 400);
    inputIds.forEach(id => {
      const el = document.getElementById(id);
      if (el) el.addEventListener('input', update);
    });
    return () => {
      clearInterval(iv);
      inputIds.forEach(id => {
        const el = document.getElementById(id);
        if (el) el.removeEventListener('input', update);
      });
    };
  }, [update]);
  const copy = () => {
    navigator.clipboard.writeText(output).then(() => {
      setCopied(true);
      setTimeout(() => setCopied(false), 2000);
    });
  };
  return <div className="rounded-xl overflow-hidden border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-900/50 my-4 shadow-sm">
      <div className="flex items-center justify-between px-4 py-2 border-b border-zinc-200 dark:border-zinc-700 bg-zinc-100/80 dark:bg-zinc-800/50 text-xs text-zinc-500 dark:text-zinc-400">
        <span className="font-medium uppercase tracking-wide">{language}</span>
        <button type="button" onClick={copy} className="px-3 py-1.5 rounded-md text-xs font-medium transition-colors bg-zinc-200/80 dark:bg-zinc-700/80 hover:bg-zinc-300 dark:hover:bg-zinc-600 text-zinc-700 dark:text-zinc-300 hover:text-zinc-900 dark:hover:text-zinc-100" style={copied ? {
    background: 'var(--mint-color-primary, #0057B7)',
    color: '#fff'
  } : {}}>{copied ? 'Copied!' : 'Copy'}</button>
      </div>
      <pre className="p-4 m-0 overflow-x-auto text-sm leading-relaxed font-mono whitespace-pre dark:!text-zinc-200 dark:!bg-zinc-900" style={{
    color: '#242628',
    backgroundColor: '#f8f9fa',
    minHeight: '2.5rem'
  }}>{output || ' '}</pre>
    </div>;
};

export const LiveCode = ({scopeId, fields}) => {
  const store = useRef(new Map());
  useEffect(() => {
    if (typeof document === 'undefined') return;
    const esc = s => String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
    const apply = () => {
      const root = document.getElementById(scopeId);
      if (!root) return;
      root.querySelectorAll('pre').forEach(b => {
        if (!store.current.has(b)) store.current.set(b, b.innerHTML);
        let html = store.current.get(b);
        fields.forEach(f => {
          const el = document.getElementById(f.id);
          const val = el ? el.value || el.placeholder || f.def : f.def;
          html = html.split(esc(f.def)).join(esc(val));
        });
        if (b.innerHTML !== html) b.innerHTML = html;
      });
    };
    apply();
    const iv = setInterval(apply, 250);
    const root = document.getElementById(scopeId);
    if (root) root.addEventListener('click', () => setTimeout(apply, 0));
    fields.forEach(f => {
      const el = document.getElementById(f.id);
      if (el) el.addEventListener('input', apply);
    });
    return () => {
      clearInterval(iv);
      fields.forEach(f => {
        const el = document.getElementById(f.id);
        if (el) el.removeEventListener('input', apply);
      });
    };
  }, [scopeId]);
  return null;
};

<Note>
  このページは **Authlete 3.0** 用です。2.x については [こちらのページ](/ja/v2/get-started/core-concepts/oauth-basics) をご覧ください。認可・トークン・イントロスペクションの API は **[API リファレンス](/api-reference)** で試すこともできます。
</Note>

# はじめに

このチュートリアルでは、OAuth 2.0 の認可コードグラントフローをサポートする認可サーバーを実装するために [Authlete API](/api-reference) を使用する基本的な方法を説明します。また、リソースサーバーがアクセス トークンを迅速に検証し、安全で認可された API へのアクセスを提供するために Authlete API を使用する方法も示します。

**前提条件:**

* OAuth 2.0 の基本的な知識
* Authlete アカウントへのアクセス権 ([必要に応じてこちらからサインアップしてください](https://console.authlete.com/register))。

# コンポーネント

一般的な OAuth 2.0 アーキテクチャには、以下のフロー図に示すように、複数のコンポーネントが含まれます。このチュートリアルでは、[Authlete 管理コンソール](https://console.authlete.com) と、米国リージョンで稼働する Authlete API クラスター (`https://us.authlete.com`) のパブリッククラウドバージョンを使用します。認可サーバーおよびリソースサーバーは `curl` コマンド等を使用してシミュレートし、認可、トークン発行、およびイントロスペクションのために Authlete へ API リクエストを行う方法を示します。

```mermaid theme={null}
flowchart LR
    subgraph EndUser [エンドユーザー]
        userAgent["ユーザーエージェント (N/A)"]
        resourceOwner["リソース所有者 (N/A)"]
    end

    subgraph APIClient [API クライアント]
        client["クライアント (N/A)"]
    end

    subgraph APIServer [API サーバー]
        authServer["認可サーバー (curl)"]
        resourceServer["リソースサーバー (curl)"]
        authAdmin["Authlete 管理者"]
    end

    subgraph Authlete [Authlete]
        mgmtConsole["Authlete 管理コンソール"]
        authAPI["Authlete API"]
    end

    resourceOwner --> userAgent
    userAgent -- "クライアントへのアクセス" --> client
    userAgent -- "認可リクエスト (ユーザー認証と同意)" --> authServer
    client -- "トークンリクエスト" --> authServer
    client -- "APIリクエスト" --> resourceServer

    %% API サーバーと Authlete のやり取り
    authAdmin -- "サービス/クライアント管理" --> mgmtConsole
    mgmtConsole -- "APIリクエスト" --> authAPI

    authServer -- "APIリクエスト" --> authAPI
    resourceServer -- "APIリクエスト" --> authAPI
```

各コンポーネントの FQDN は以下の通りです。認可サーバーおよびクライアントは `curl` でシミュレーションするため実際には FQDN は利用されませんが、以下の値を使用して OAuth フローを説明します。

| コンポーネント                | FQDN                   |
| ---------------------- | ---------------------- |
| Authlete API - 米国クラスター | `us.authlete.com`      |
| Authlete 管理コンソール       | `console.authlete.com` |
| 認可サーバー                 | `as.example.com`       |
| クライアント                 | `client.example.org`   |
| リソースサーバー               | N/A                    |

# 環境セットアップ

「[API を使ってみる](/ja/get-started/quickstarts/getting-started)」に従って、新しい Authlete サービスとクライアントを作成してください。また、Authlete API の呼び出しに必要なサービスアクセストークンを生成してください。以下の入力欄に値を入れると、以降のサンプルコード（コマンド）が自動で更新されます。（SDK タブのサービスアクセストークンは、ハードコードではなく環境変数 `SERVICE_ACCESS_TOKEN` から読み込む形にしています。）

<InputTable
  rows={[
{ label: "Authlete API - 米国クラスター", id: "apiClusterHost", defaultValue: "https://us.authlete.com" },
{ label: "サービス ID", id: "serviceID", defaultValue: "933860280" },
{ label: "サービスアクセストークン", id: "serviceAccessToken", defaultValue: "DL7jo1z3iUIXyI5MnX" },
{ label: "クライアント ID", id: "clientID", defaultValue: "12818600553323" },
{ label: "クライアントシークレット", id: "clientSecret", defaultValue: "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w" },
{ label: "リダイレクト URI", id: "redirectUri", defaultValue: "https://client.example.org/cb/example.com" },
{ label: "クライアントタイプ", static: "CONFIDENTIAL" },
{ label: "クライアント認証方法", static: "CLIENT_SECRET_BASIC" },
]}
/>

# ウォークスルー

以下のシーケンス図は、このチュートリアルで使用される OAuth 2.0 フロー全体を示しています。各ステップを進める際に参照してください。

```mermaid theme={null}
sequenceDiagram
    autonumber
    participant RO as リソース所有者
    participant UA as ユーザーエージェント
    participant C as クライアント
    participant AS as 認可サーバー
    participant RS as リソースサーバー
    participant API as Authlete API

    RO ->> UA: 開始
    UA ->> C: リクエストを実行
    C -->> UA: 認可リクエスト
    UA ->> AS: リクエストを転送
    AS ->> API: POST /auth/authorization
    API -->> AS: 処理可否結果<br/>(チケットを含む)
    AS -->> UA: ユーザー認証および同意ページ
    RO <<->> UA: 認証情報を入力
    UA ->> AS: ログインおよび同意
    AS ->> API: POST /auth/authorization/issue<br/>(チケットを含む)
    API -->> AS: 認可応答内容<br/>(認可コードを含む)
    AS -->> UA: 認可応答
    UA ->> C: 応答を転送
    C ->> AS: トークンリクエスト<br/>(認可コードを含む)
    AS ->> API: POST /auth/token
    API -->> AS: トークン応答内容<br/>(アクセストークンを含む)
    AS -->> C: トークン応答
    C ->> RS: 保護されたリソースへのアクセス<br/>(アクセストークンを含む)
    RS ->> API: POST /auth/introspection
    API -->> RS: トークン検証応答
    RS -->> C: クライアントへの応答
    C -->> UA: コンテンツ
    UA -->> RO: 終了
```

## クライアントから認可サーバーへの認可リクエスト

クライアントは、ユーザーエージェントを介して認可サーバーに認可リクエストを行います（ステップ 3 および 4）。
このチュートリアルでは、リクエストのパラメーターとして以下の値が指定されているものと仮定します（**環境セットアップ**の入力欄の値が反映されています）。

<InputTable
  col1="パラメーター"
  col2="値"
  rows={[
{ code: "client_id", reflect: "clientID" },
{ code: "response_type", static: "code" },
{ code: "redirect_uri", reflect: "redirectUri" },
]}
/>

本記事では、認可サーバーはユーザーエージェントから以下のような HTTP GET クエリ文字列を受け取るとします（可読性のため折り返しを追加しています）。

<DynamicCurl inputIds={['clientID', 'redirectUri']} language="shell">
  {`redirect_uri=$2\n &response_type=code\n &client_id=$1`}
</DynamicCurl>

一般的な認可サーバーは、以下のルールを評価してから認可コードグラントフローを進める必要があります。

* クライアント ID 「<TextReflect id="clientID" />」に関連付けられたクライアントが認可サーバーに登録されていることを確認する。
* リダイレクト URI 「<TextReflect id="redirectUri" />」がクライアントに登録された URI のいずれかと一致することを確認する。
* `response_type` や `scope` などの他のパラメーター値がクライアントによってリクエスト内で指定されることが許可されていることを確認する。

認可サーバーに代わってこれらのリクエスト検証を実行するのが、Authlete の [`/auth/authorization`](/api-reference/authorization-endpoint/process-authorization-request) API です。

Authlete API にリクエストを送信する（ステップ 5）ためのコマンド/コードを、以下の言語タブから選択してください。各タブの値は、前述の入力欄の値を変えると自動更新されます。

<div id="cg-authz">
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://us.authlete.com/api/933860280/auth/authorization" \
         -H "Authorization: Bearer DL7jo1z3iUIXyI5MnX" \
         -H "Content-Type: application/json" \
         -d '{ "parameters": "response_type=code&client_id=12818600553323&redirect_uri=https://client.example.org/cb/example.com"}'
    ```

    ```powershell PowerShell theme={null}
    $headers = @{ Authorization = "Bearer DL7jo1z3iUIXyI5MnX" }
    $body = '{ "parameters": "response_type=code&client_id=12818600553323&redirect_uri=https://client.example.org/cb/example.com" }'
    Invoke-RestMethod -Method Post `
      -Uri "https://us.authlete.com/api/933860280/auth/authorization" `
      -Headers $headers -ContentType "application/json" -Body $body
    ```

    ```java Java theme={null}
    // authlete-java-common — クライアントを一度初期化し、以降のステップで再利用します。
    AuthleteConfiguration config = new AuthleteSimpleConfiguration()
            .setApiVersion("V3")
            .setBaseUrl("https://us.authlete.com")
            .setServiceApiKey("933860280")
            .setServiceAccessToken(System.getenv("SERVICE_ACCESS_TOKEN"));
    AuthleteApi authleteApi = AuthleteApiFactory.create(config);

    AuthorizationRequest request = new AuthorizationRequest()
            .setParameters("response_type=code"
                    + "&client_id=12818600553323"
                    + "&redirect_uri=https://client.example.org/cb/example.com");

    AuthorizationResponse response = authleteApi.authorization(request);
    System.out.println(response.getAction()); // INTERACTION
    System.out.println(response.getTicket());
    ```

    ```typescript TypeScript theme={null}
    import { Authlete } from "@authlete/typescript-sdk";

    // クライアントを一度初期化し、以降のステップで再利用します。
    const authlete = new Authlete({
      serverURL: "https://us.authlete.com",
      bearer: process.env["SERVICE_ACCESS_TOKEN"] ?? "",
    });
    const SERVICE_ID = "933860280";

    const res = await authlete.authorization.processRequest({
      serviceId: SERVICE_ID,
      authorizationRequest: {
        parameters:
          "response_type=code&client_id=12818600553323&redirect_uri=https://client.example.org/cb/example.com",
      },
    });
    console.log(res.action); // "INTERACTION"
    console.log(res.ticket);
    ```

    ```ruby Ruby theme={null}
    require "authlete_ruby_sdk"

    # クライアントを一度初期化し、以降のステップで再利用します。
    client     = Authlete::Client.new(
      bearer:     ENV.fetch("SERVICE_ACCESS_TOKEN"),
      server_url: "https://us.authlete.com"
    )
    SERVICE_ID = "933860280"

    req = Authlete::Models::Components::AuthorizationRequest.new(
      parameters: "response_type=code&client_id=12818600553323&redirect_uri=https://client.example.org/cb/example.com"
    )
    res = client.authorization.process_request(service_id: SERVICE_ID, authorization_request: req)

    puts res.authorization_response.action # "INTERACTION"
    puts res.authorization_response.ticket
    ```

    ```go Go theme={null}
    package main

    import (
    	"context"
    	"log"
    	"os"

    	authlete "github.com/authlete/authlete-go-sdk"
    	"github.com/authlete/authlete-go-sdk/models/components"
    )

    func main() {
    	ctx := context.Background()

    	// クライアントを一度初期化し、以降のステップで再利用します。
    	s := authlete.New(
    		authlete.WithServerURL("https://us.authlete.com"),
    		authlete.WithSecurity(os.Getenv("SERVICE_ACCESS_TOKEN")),
    	)
    	const serviceID = "933860280"

    	res, err := s.Authorization.ProcessRequest(ctx, serviceID, components.AuthorizationRequest{
    		Parameters: "response_type=code&client_id=12818600553323&redirect_uri=https://client.example.org/cb/example.com",
    	})
    	if err != nil {
    		log.Fatal(err)
    	}
    	log.Printf("action=%v", *res.AuthorizationResponse.Action) // INTERACTION
    	log.Printf("ticket=%s", *res.AuthorizationResponse.Ticket)
    }
    ```
  </CodeGroup>
</div>

<LiveCode
  scopeId="cg-authz"
  fields={[
{ id: "apiClusterHost", def: "https://us.authlete.com" },
{ id: "serviceID", def: "933860280" },
{ id: "serviceAccessToken", def: "DL7jo1z3iUIXyI5MnX" },
{ id: "clientID", def: "12818600553323" },
{ id: "redirectUri", def: "https://client.example.org/cb/example.com" },
]}
/>

リクエストが有効である場合、Authlete は以下のような応答を返します（ステップ 6）。

```json theme={null}
{
    "action": "INTERACTION",
    "resultCode": "A004001",
    "resultMessage": "[A004001] Authlete has successfully issued a ticket to the service (API Key = 933860280) for the authorization request from the client (ID = 12818600553323). [response_type=code, openid=false]",
    "ticket": "cElOaH9j4mS6AiIGR9oLqHlDn9jpvcNjqSgyRqfcmAE",
    "client": { "..." },
    "service": { "..." },
}
```

* `resultMessage` は、リクエスト処理の結果を人間が読める形式で提供します（詳細は [Authlete の結果コードの解釈](/ja/configuration-reference/error-handling-debugging/interpreting-authletes-result-codes) を参照）。
* `action` は、認可サーバーが次に何を行うべきかを示します（詳細は [アクションハンドリング](/ja/get-started/concepts/action-handling) を参照）。
* `ticket` は、次のステップで別の API にリクエストを行うために認可サーバーが必要とする値です（詳細は [2 段階の API 呼び出し](/ja/get-started/concepts/two-step-api-calls) を参照）。

Authlete からのレスポンスには、サービスおよびクライアント情報も含まれています。認可サーバーはこれらの情報を利用して、リソースオーナー（エンドユーザー）に、サービスへのアクセスをクライアントに許可するかどうかを確認することになります。

## ユーザー認証とアクセス許可の確認

リソース所有者と認可サーバーの間の実際のやり取りは、このチュートリアルの範囲外です。通常、認可サーバーはユーザーの資格情報（例: ID とパスワード）を使用してユーザーを認証し、ユーザーの役割や権限を特定し、クライアントにアクセスを許可するかどうかを尋ねます（ステップ 7、8、9）。

## 認可コードの発行

ここまでの処理を経て、認可サーバーが次の状態に達したと仮定します。

* 認可サーバーはリソース所有者を認証し、`subject` パラメーターの値として Authlete に共有する識別子が `testuser01` であることを確定した。
* 認可サーバーはリソース所有者から同意を得た。

認可サーバーは認可コードを発行するために、Authlete の [`/auth/authorization/issue`](/api-reference/authorization-endpoint/issue-authorization-response) にリクエストを行います。このときのリクエストパラメーターに、`subject` および `/auth/authorization` API 応答の `ticket` の値を含めます（ステップ 10）。

以下の入力欄に、さきほどの `/auth/authorization` API のレスポンスに含まれていた `ticket` と、任意の `subject` の値を入れてください（各タブのコードが更新されます）。

<InputTable
  rows={[
{ label: "チケット", id: "ticket", defaultValue: "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc" },
{ label: "サブジェクト", id: "subject", defaultValue: "testuser01" },
]}
/>

<div id="cg-issue">
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://us.authlete.com/api/933860280/auth/authorization/issue" \
         -H "Authorization: Bearer DL7jo1z3iUIXyI5MnX" \
         -H "Content-Type: application/json" \
         -d '{ "ticket": "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc","subject": "testuser01"}'
    ```

    ```powershell PowerShell theme={null}
    $headers = @{ Authorization = "Bearer DL7jo1z3iUIXyI5MnX" }
    $body = '{ "ticket": "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc", "subject": "testuser01" }'
    Invoke-RestMethod -Method Post `
      -Uri "https://us.authlete.com/api/933860280/auth/authorization/issue" `
      -Headers $headers -ContentType "application/json" -Body $body
    ```

    ```java Java theme={null}
    // 認可ステップで作成した authleteApi クライアントを再利用します。
    AuthorizationIssueRequest request = new AuthorizationIssueRequest()
            .setTicket("5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc")
            .setSubject("testuser01");

    AuthorizationIssueResponse response = authleteApi.authorizationIssue(request);
    System.out.println(response.getAction());           // LOCATION
    System.out.println(response.getAuthorizationCode());
    System.out.println(response.getResponseContent());
    ```

    ```typescript TypeScript theme={null}
    // 認可ステップで作成した authlete クライアントと SERVICE_ID を再利用します。
    const res = await authlete.authorization.issue({
      serviceId: SERVICE_ID,
      authorizationIssueRequest: {
        ticket: "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc",
        subject: "testuser01",
      },
    });
    console.log(res.action);            // "LOCATION"
    console.log(res.authorizationCode);
    console.log(res.responseContent);
    ```

    ```ruby Ruby theme={null}
    # 認可ステップで作成した client と SERVICE_ID を再利用します。
    req = Authlete::Models::Components::AuthorizationIssueRequest.new(
      ticket:  "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc",
      subject: "testuser01"
    )
    res = client.authorization.issue_response(service_id: SERVICE_ID, authorization_issue_request: req)

    puts res.authorization_issue_response.action            # "LOCATION"
    puts res.authorization_issue_response.authorization_code
    puts res.authorization_issue_response.response_content
    ```

    ```go Go theme={null}
    // 認可ステップで作成した s クライアントと serviceID を再利用します。
    res, err := s.Authorization.Issue(ctx, serviceID, components.AuthorizationIssueRequest{
    	Ticket:  "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc",
    	Subject: "testuser01",
    })
    if err != nil {
    	log.Fatal(err)
    }
    log.Printf("action=%v", *res.AuthorizationIssueResponse.Action) // LOCATION
    log.Printf("authorizationCode=%s", *res.AuthorizationIssueResponse.AuthorizationCode)
    log.Printf("responseContent=%s", *res.AuthorizationIssueResponse.ResponseContent)
    ```
  </CodeGroup>
</div>

<LiveCode
  scopeId="cg-issue"
  fields={[
{ id: "apiClusterHost", def: "https://us.authlete.com" },
{ id: "serviceID", def: "933860280" },
{ id: "serviceAccessToken", def: "DL7jo1z3iUIXyI5MnX" },
{ id: "ticket", def: "5tqii9i_pUp1iteacZGUtdjikRnqGSrPwW7lqoH1Pcc" },
{ id: "subject", def: "testuser01" },
]}
/>

リクエストが有効である場合、Authlete は以下のような応答を生成します（ステップ 11）。

```json theme={null}
{
    "action": "LOCATION",
    "authorizationCode": "tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI",
    "idToken": null,
    "jwtAccessToken": null,
    "responseContent": "https://client.example.org/cb/example.com?code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&iss=https%3A%2F%2Fauthlete.com",
    "resultCode": "A040001",
    "resultMessage": "[A040001] The authorization request was processed successfully.",
    "ticketInfo": {
        "context": null
    }
}
```

* `resultMessage` は、リクエスト処理の結果を人間が読める形式で提供します（詳細は [Authlete の結果コードの解釈](/ja/configuration-reference/error-handling-debugging/interpreting-authletes-result-codes) を参照）。
* `action` は、認可サーバーが次に何を行うべきかを示します。この応答では `LOCATION` という値が指定されており、認可サーバーはユーザーエージェントにリダイレクト応答を返す必要があります（詳細は [アクションハンドリング](/ja/get-started/concepts/action-handling) を参照）。
* `responseContent` は、認可サーバーからの応答内容を表します（詳細は [アクションハンドリング](/ja/get-started/concepts/action-handling) を参照）。

認可サーバーはユーザーエージェントに以下のような応答を送信することが期待されます（ステップ 12）。

```http theme={null}
HTTP/1.1 302 Found
Location: https://client.example.org/cb/example.com
 ?code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI
```

クライアントにトークンを発行しないこともあります。たとえば、前の認証やユーザーの同意確認の結果、またはトークン発行を妨げるその他の文脈が欠落している場合です。このような場合、認可サーバーはプロトコル応答を通じて認可フローが終了したことをクライアントに通知する必要があります。

Authlete の [`/auth/authorization/fail`](/api-reference/authorization-endpoint/fail-authorization-response) API は、クライアントに送信されるメッセージや応答の転送方法に関する終了プロセスをサポートします。

まとめると、認可サーバーは通常、ユーザー認証および同意の結果に応じて `/auth/authorization/issue` または `/auth/authorization/fail` API のいずれかを呼び出します。

## トークンリクエスト

ここでは、ユーザーエージェントが認可サーバーからのリダイレクト応答を受信したものと仮定します。その後、以下のようなリクエストがクライアントに送信されます（ステップ 13）。（可読性のため折り返しを追加しています。入力欄の認可コードを入れると下の GET が更新されます。）

以下の入力欄に**認可コード**（前ステップの応答で得た `code`）を入れると、コードが更新されます。

<InputTable
  rows={[
{ label: "認可コード (code)", id: "authzCode", defaultValue: "tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI" },
]}
/>

<DynamicCurl inputIds={['authzCode']} language="http">
  {`GET /cb/example.com?code=$1 HTTP/1.1\nHost: client.example.org`}
</DynamicCurl>

クライアントは `code` パラメーターの値を抽出し、この値を使用してトークンリクエストを作成し、以下のように認可サーバーに送信します。このチュートリアルでは、`https://as.example.com/token` をトークンエンドポイント URI と仮定します（ステップ 14）。

<DynamicCurl inputIds={['clientID', 'clientSecret', 'authzCode', 'redirectUri']} language="http">
  {`POST /token HTTP/1.1\nHost: as.example.com\nAuthorization: Basic base64($1:$2)\nContent-Type: application/x-www-form-urlencoded\n\ngrant_type=authorization_code\n &code=$3\n &redirect_uri=$4`}
</DynamicCurl>

認可サーバーはリクエスト内のパラメーターを評価し、その後、クライアントにトークン応答を返します。
このチュートリアルでは、リクエストを評価し、応答を生成するために、Authlete の [`/auth/token`](/api-reference/token-endpoint/process-token-request) API を使用します。

<div id="cg-token">
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://us.authlete.com/api/933860280/auth/token" \
         -H "Authorization: Bearer DL7jo1z3iUIXyI5MnX" \
         -H "Content-Type: application/json" \
         -d '{ "clientId": "12818600553323","clientSecret": "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w","parameters": "grant_type=authorization_code&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&redirect_uri=https://client.example.org/cb/example.com" }'
    ```

    ```powershell PowerShell theme={null}
    $headers = @{ Authorization = "Bearer DL7jo1z3iUIXyI5MnX" }
    $body = '{ "clientId": "12818600553323", "clientSecret": "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w", "parameters": "grant_type=authorization_code&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&redirect_uri=https://client.example.org/cb/example.com" }'
    Invoke-RestMethod -Method Post `
      -Uri "https://us.authlete.com/api/933860280/auth/token" `
      -Headers $headers -ContentType "application/json" -Body $body
    ```

    ```java Java theme={null}
    // 認可ステップで作成した authleteApi クライアントを再利用します。
    TokenRequest request = new TokenRequest()
            .setClientId("12818600553323")
            .setClientSecret("AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w")
            .setParameters("grant_type=authorization_code"
                    + "&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI"
                    + "&redirect_uri=https://client.example.org/cb/example.com");

    TokenResponse response = authleteApi.token(request);
    System.out.println(response.getAction());          // OK
    System.out.println(response.getAccessToken());
    System.out.println(response.getResponseContent());
    ```

    ```typescript TypeScript theme={null}
    // 認可ステップで作成した authlete クライアントと SERVICE_ID を再利用します。
    const res = await authlete.token.process({
      serviceId: SERVICE_ID,
      tokenRequest: {
        clientId: "12818600553323",
        clientSecret:
          "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w",
        parameters:
          "grant_type=authorization_code&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&redirect_uri=https://client.example.org/cb/example.com",
      },
    });
    console.log(res.action);          // "OK"
    console.log(res.accessToken);
    console.log(res.responseContent);
    ```

    ```ruby Ruby theme={null}
    # 認可ステップで作成した client と SERVICE_ID を再利用します。
    req = Authlete::Models::Components::TokenRequest.new(
      client_id:     "12818600553323",
      client_secret: "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w",
      parameters:    "grant_type=authorization_code&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&redirect_uri=https://client.example.org/cb/example.com"
    )
    res = client.tokens.process_request(service_id: SERVICE_ID, token_request: req)

    puts res.token_response.action           # "OK"
    puts res.token_response.access_token
    puts res.token_response.response_content
    ```

    ```go Go theme={null}
    // 認可ステップで作成した s クライアントと serviceID を再利用します。
    res, err := s.Token.Process(ctx, serviceID, components.TokenRequest{
    	ClientID:     types.String("12818600553323"), // types: github.com/authlete/authlete-go-sdk/types
    	ClientSecret: types.String("AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w"),
    	Parameters:   "grant_type=authorization_code&code=tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI&redirect_uri=https://client.example.org/cb/example.com",
    })
    if err != nil {
    	log.Fatal(err)
    }
    log.Printf("action=%v", *res.TokenResponse.Action) // OK
    log.Printf("accessToken=%s", *res.TokenResponse.AccessToken)
    log.Printf("responseContent=%s", *res.TokenResponse.ResponseContent)
    ```
  </CodeGroup>
</div>

<LiveCode
  scopeId="cg-token"
  fields={[
{ id: "apiClusterHost", def: "https://us.authlete.com" },
{ id: "serviceID", def: "933860280" },
{ id: "serviceAccessToken", def: "DL7jo1z3iUIXyI5MnX" },
{ id: "clientID", def: "12818600553323" },
{ id: "clientSecret", def: "AqZKLbUFfRurwZ0-zN1BwAXfo-odPwrZa6XoSOrm-AKtbaaOAq6uzWM3oVG4w" },
{ id: "authzCode", def: "tJlGEKt8y0DLpjIA_jweywRtJs2fh83ZGNiwFRmIwYI" },
{ id: "redirectUri", def: "https://client.example.org/cb/example.com" },
]}
/>

リクエストが有効である場合、Authlete は以下のような応答を返します（ステップ 16）。

```json theme={null}
{
    "accessToken": "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g",
    "accessTokenDuration": 86400,
    "accessTokenExpiresAt": 1730203012061,
    "action": "OK",
    "clientAuthMethod": "CLIENT_SECRET_BASIC",
    "clientId": 12818600553323,
    "grantType": "AUTHORIZATION_CODE",
    "refreshToken": "1mQRm2XRwaBNYRIjxjO0ls9hb4eDKo9I3ChU0yy8lHo",
    "refreshTokenDuration": 864000,
    "refreshTokenExpiresAt": 1730980612061,
    "responseContent": "{\"access_token\":\"gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g\",\"token_type\":\"Bearer\",\"expires_in\":86400,\"scope\":null,\"refresh_token\":\"1mQRm2XRwaBNYRIjxjO0ls9hb4eDKo9I3ChU0yy8lHo\"}",
    "resultCode": "A050001",
    "resultMessage": "[A050001] The token request (grant_type=authorization_code) was processed successfully.",
    "subject": "testuser01",
}
```

* `resultMessage` は、リクエスト処理の結果を人間が読める形式で提供します（詳細は [Authlete の結果コードの解釈](/ja/configuration-reference/error-handling-debugging/interpreting-authletes-result-codes) を参照）。
* `action` は、認可サーバーが次に何を行うべきかを示します。この応答では `OK` という値が指定されており、認可サーバーはクライアントにトークン応答を送信する必要があります（詳細は [アクションハンドリング](/ja/get-started/concepts/action-handling) を参照）。
* `responseContent` には、認可サーバーからの応答内容が含まれています（詳細は [アクションハンドリング](/ja/get-started/concepts/action-handling) を参照）。

認可サーバーは以下のような応答をクライアントに送信することが期待されます（ステップ 17）。

```http theme={null}
HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token":"gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g",
  "refresh_token":"1mQRm2XRwaBNYRIjxjO0ls9hb4eDKo9I3ChU0yy8lHo",
  "scope":null,
  "token_type":"Bearer",
  "expires_in":86400
}
```

<Info>以上で、認可サーバーはトークンを正常に作成・提供したことになります。このように Authlete API を利用することで、認可およびトークンリクエストのパラメーターを評価するための複雑なロジックを実装することなく、適切な方法で応答する認可サーバーを構築できます。</Info>

## API リクエスト（トークンイントロスペクション）

通常、クライアントはアクセストークンを使用してリソースサーバーにリクエストを送信し、API にアクセスします（ステップ 18）。リソースサーバーは、トークンの有効性を評価し、トークンに関連付けられたユーザーおよびクライアントに関する情報を取得し、API リクエストへの応答方法を決定します。

Authlete は、この目的のために [`/auth/introspection`](/api-reference/introspection-endpoint/process-introspection-request) API を提供しています。この API はトークンの有効性を検証し、必要な情報を提供します。

リソースサーバーがクライアントからアクセストークンを受け取ったと仮定し、そのアクセストークンを以下の入力欄に設定して、Authlete API を用いたイントロスペクションを実行してみましょう。

<InputTable
  rows={[
{ label: "アクセストークン (access_token)", id: "accessToken", defaultValue: "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g" },
]}
/>

<div id="cg-introspect">
  <CodeGroup>
    ```bash cURL theme={null}
    curl -X POST "https://us.authlete.com/api/933860280/auth/introspection" \
         -H "Authorization: Bearer DL7jo1z3iUIXyI5MnX" \
         -H "Content-Type: application/json" \
         -d '{ "token": "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g" }'
    ```

    ```powershell PowerShell theme={null}
    $headers = @{ Authorization = "Bearer DL7jo1z3iUIXyI5MnX" }
    $body = '{ "token": "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g" }'
    Invoke-RestMethod -Method Post `
      -Uri "https://us.authlete.com/api/933860280/auth/introspection" `
      -Headers $headers -ContentType "application/json" -Body $body
    ```

    ```java Java theme={null}
    // 認可ステップで作成した authleteApi クライアントを再利用します。
    IntrospectionRequest request = new IntrospectionRequest()
            .setToken("gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g");

    IntrospectionResponse response = authleteApi.introspection(request);
    System.out.println(response.getAction());   // OK
    System.out.println(response.isUsable());     // true
    System.out.println(response.getSubject());   // testuser01
    System.out.println(response.getClientId());  // 12818600553323 (long)
    ```

    ```typescript TypeScript theme={null}
    // 認可ステップで作成した authlete クライアントと SERVICE_ID を再利用します。
    const res = await authlete.introspection.process({
      serviceId: SERVICE_ID,
      introspectionRequest: {
        token: "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g",
      },
    });
    console.log(res.action);   // "OK"
    console.log(res.subject);  // "testuser01"
    console.log(res.clientId); // 12818600553323 (number)
    ```

    ```ruby Ruby theme={null}
    # 認可ステップで作成した client と SERVICE_ID を再利用します。
    req = Authlete::Models::Components::IntrospectionRequest.new(
      token: "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g"
    )
    res = client.introspection.process_request(service_id: SERVICE_ID, introspection_request: req)

    puts res.introspection_response.action    # "OK"
    puts res.introspection_response.subject   # "testuser01"
    puts res.introspection_response.client_id # 12818600553323 (Integer)
    ```

    ```go Go theme={null}
    // 認可ステップで作成した s クライアントと serviceID を再利用します。
    res, err := s.Introspection.Process(ctx, serviceID, components.IntrospectionRequest{
    	Token: "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g",
    })
    if err != nil {
    	log.Fatal(err)
    }
    log.Printf("action=%v", *res.IntrospectionResponse.Action)    // OK
    log.Printf("subject=%s", *res.IntrospectionResponse.Subject)  // testuser01
    log.Printf("clientId=%d", *res.IntrospectionResponse.ClientID) // 12818600553323 (int64)
    ```
  </CodeGroup>
</div>

<LiveCode
  scopeId="cg-introspect"
  fields={[
{ id: "apiClusterHost", def: "https://us.authlete.com" },
{ id: "serviceID", def: "933860280" },
{ id: "serviceAccessToken", def: "DL7jo1z3iUIXyI5MnX" },
{ id: "accessToken", def: "gV5ByjqCVjyinFugV9Wlc0oAxkEP0osifSajXSfJV9g" },
]}
/>

リクエストが有効である場合、Authlete は以下のような応答を返します（ステップ 20）。

```json theme={null}
{
    "action": "OK",
    "authTime": 0,
    "clientId": 12818600553323,
    "expiresAt": 1730203012000,
    "grantType": "AUTHORIZATION_CODE",
    "issuableCredentials": null,
    "properties": null,
    "refreshable": true,
    "resources": null,
    "responseContent": "Bearer error=\"invalid_request\"",
    "resultCode": "A056001",
    "resultMessage": "[A056001] The access token is valid.",
    "scopeDetails": null,
    "scopes": null,
    "serviceAttributes": null,
    "subject": "testuser01",
    "sufficient": false,
    "usable": true
}
```

リソースサーバーは、この応答から以下のような情報を取得できます。

* トークンの有効期限（`expiresAt`）
* アクセスを承認したユーザーの識別子（`subject`）
* トークンを取得するために使用されたグラントタイプ（`grantType`）
* クライアント識別子（`clientId`）

リソースサーバーはこれらの情報を使用して、API リクエストへの応答方法を決定します（ステップ 21）。

## まとめ

このチュートリアルでは、認可コードグラントフローを認可サーバーに実装するための、Authlete API の使用方法を説明しました。

## 次のステップ

Authlete の以下の機能をさらに探索してみましょう。

* [Authlete API チュートリアル - OIDC Basics](/ja/get-started/quickstarts/oidc-basics)
* [PKCE (RFC 7636)](/ja/protocols-and-flows/protocol-extensions/proof-key-for-code-exchange-pkce)
* [認可サーバーの参照実装](https://github.com/authlete/java-oauth-server)
