← レポート一覧
CONNECTOR AUTH FLOW / ELI5

connectors.github.listIssues() の裏側

社員ごとに違う Issue が返ってくるとき、認証はどこで・何回・何に対して起きているのか。
この1行だけを追いかけます。

1 / 登場するもの

トークンは2種類ある。アプリが触れるのは片方だけ

混乱のもとは、「認証」と呼ばれるものが2種類あることです。社員が誰かを示す Entra のトークンと、GitHub に対して使う GitHub のトークン。この2つは別物で、置き場所も違います。

Entra のトークン 「私は営業部の A です」という身分証 oid: 社員の ID tid: 会社のテナント aud: 基盤の API exp: 1時間程度 持ち歩く: ブラウザ → アプリ → API Management できること: 「誰か」を証明するだけ これで GitHub は呼べない アプリはこれだけを持つ GitHub のトークン GitHub が「この人の代わりに動いてよい」と認めた鍵 gho_xxxx…(A さんの分) scope: repo:read + refresh token 置き場所: API Management の Credential Manager だけ できること: GitHub を A さんとして呼ぶ 人数分ある(1人1本) アプリには一度も渡らない
左は「誰か」を示すだけの身分証で、アプリはこれを運ぶ。右は GitHub を動かす鍵で、関所の中から出ない。
アプリのコードに書く connectors.github.listIssues() は、「私(身分証)の代わりに、私の鍵で GitHub を呼んでください」と関所に頼む1行です。
2 / 一回の呼び出し

クリックから Issue 一覧が出るまでの8歩

営業部の A さんが「自分の Issue」ボタンを押したときに起きることを、順番に描きます。認証らしいものは 3 か所(2・4・7)で起きます

A さんのブラウザ アプリ(+SDK) API Management GitHub 1 Entra にログイン(SSO) Easy Auth が入口で挟む。初回だけ 2 「自分の Issue」をクリック 本人の Entra トークンが一緒に届く 3 GET /github/issues Authorization: Bearer <A さんの Entra トークン> 4 JWT を検証 署名・テナント・宛先。このアプリは許可済みか (manifest で承認したコネクタか) 5 A さんの GitHub トークンを取り出す Credential Manager から oid で検索 期限切れなら refresh token で更新 6 GET /repos/org/x/issues Authorization を GitHub トークンに差し替え 許可リポジトリ・読み取りパスだけ通す 7 A さんが見られる Issue だけ GitHub 側がトークンの持ち主で判定 8 マスクして返す 「A さんが app-1 で x の Issue を読んだ」を記録 一覧を表示
本人の身分証(Entra トークン)は 2 → 3 で右へ運ばれ、4 で検証される。GitHub の鍵は 5 で取り出され、6 で使われる。両者が交差するのは API Management の中だけ。
2
誰か: Entra のログイン
Easy Auth が入口で Entra のログインを挟む。アプリはコードを書かずに、本人の Entra トークン(または本人情報のヘッダー)を受け取る。SDK はこれをそのまま関所へ運ぶ。
4
呼んでよいか: API Management の検証
JWT の署名と発行元が正しいか、宛先が基盤の API か、このアプリが manifest で github: [issues:read] を承認されているか。ここで落ちれば GitHub には何も届かない。
7
何が見えるか: GitHub 側の判定
API Management が付けたのは A さん本人の GitHub トークンなので、GitHub は A さんがブラウザで GitHub を開いたときと同じ範囲だけを返す。アプリ側に絞り込みの if 文は要らない。
3 / なぜ人ごとに違うか

「誰のトークンで GitHub を呼ぶか」で、2つの方式がある

社員ごとに返る Issue が違う、を実現するやり方は2つあります。どちらもアプリのコードは同じ1行で、違いは関所の設定だけです。

方式 1: 共有アカウント A さん B さん 関所 鍵は1本だけ GitHub 返ってくるのは全員同じ 「基盤アカウント」が見られる全 Issue 人ごとに違えるには、アプリの if 文で絞る issues.filter(i => i.assignee == me) これは「権限」ではなく「表示の工夫」。 アプリのバグ1つで、他人の Issue が見える 方式 2: 本人紐付け(user-delegated) A さん B さん 関所 A さんの鍵 関所 B さんの鍵 GitHub 返ってくるのは人ごとに違う GitHub が「この鍵の持ち主」に見せてよい分だけ 絞り込みは GitHub の権限そのもの アプリの if 文は不要。バグがあっても、 本人が GitHub で見られない Issue は出てこない 代わりに、各社員が初回に1度「連携」する(次章) アプリのコードはどちらも connectors.github.listIssues() のまま。差は関所の設定と、初回の連携があるかどうか
左は鍵が1本、右は人数分。「人ごとに違うべきデータ」は右で扱い、「全員同じでよいデータ」(公開リポジトリのリリース一覧など)は左で足りる。
方式 1 を使う場面

全員に同じものを見せてよいとき

社内公開リポジトリの Issue 一覧、リリースノート、CI の状態。GitHub 側で「見せてよい人」が全社員と一致しているデータ。管理者が一度ログインするだけで済む。

方式 2 を使う場面

人によって見えてよい範囲が違うとき

プライベートリポジトリ、自分に割り当てられた Issue、自分の PR。「GitHub で本人が見られる範囲」をそのまま基盤の権限にしたいとき。今回の質問はこちら。

manifest での書き分け

コネクタごとに宣言する

方式 1 は shared、方式 2 は per-user。承認するときに管理者が確認する項目になる。

connectors:
  github:
    mode: per-user
    scopes: [issues:read]
    repos: [org/x, org/y]
4 / 初回だけの手順

「GitHub と連携する」を1人1回。鍵はここで関所に預ける

方式 2 では、各社員が最初に一度だけ GitHub の同意画面を通ります。このときに作られた GitHub トークンは、社員のブラウザにもアプリにも渡らず、API Management の Credential Manager に保存されます

A さんのブラウザ アプリ(+SDK) API Management GitHub 1 listIssues()(本人トークン付き) 2 A さん(oid)の接続が無い 接続を新規作成し、ログインリンクを発行 3 「連携が必要」+ リンク SDK がボタンを表示 「GitHub と連携する」 4 GitHub の同意画面へ(アプリを経由しない) 「基盤 が repo:read でアクセスすることを許可しますか」に A さんが許可 5 A さんのトークンを発行 コールバック先は API Management。ブラウザにもアプリにも渡らない 6 Credential Manager に保存 oid ↔ GitHub トークン。暗号化、Key Vault の鍵で 7 listIssues() をやり直す 今度は第 2 章の 4 → 8 が普通に進む 2回目以降: 同意画面なし。トークンの期限切れも API Management が refresh token で自動更新
GitHub のトークンが移動するのは 5 の一本だけで、行き先は API Management。社員が見るのは 4 の同意画面だけ。
API Management がやること

保存・暗号化・更新・付与

Credential Manager が、接続ごとのトークンを暗号化して保管し、期限が近づけば refresh token で更新し、呼び出し時に get-authorization-context ポリシーで取り出して付与する。基盤チームがトークンを扱うコードを書く必要はない

基盤チームが書くもの

「接続を作ってリンクを返す」小さな処理

2 の部分。API Management の Authorization REST API で oid を名前にした接続を作り、ログインリンクを取得して返す。ポリシー側は authorization-id に oid を入れる式を書く。SDK 側は「連携が必要」を受けたらボタンを出す。

GitHub 側に必要なもの

基盤用の OAuth App が1つ

Client ID と Client Secret は Credential Manager の「資格情報プロバイダー」に登録する。この Secret も社員には渡らない。同意画面に出る名前はこの OAuth App の名前。

5 / 触って確かめる

模擬デモ: 社員を切り替えて、同じ1行を呼んでみる

ここまでの流れを、このページの中だけで動かせます。GitHub や Entra とは通信しません。ブラウザ・アプリ・関所・GitHub の4つの役をこのページが演じ、どこにどのトークンがあるかを表示します。

おすすめの順番: 方式 2 のまま A さんでログインして「自分の Issue」を押す(初回連携が出る) → B さんに切り替えて同じことをする → 方式 1 に切り替えて「アプリの if 文」を外す → A さんの Entra を無効化してもう一度押す。

画面(社員が見るもの)
社員
提案書管理 / 自分の Issue未ログイン
設定(管理者・基盤チームがいじる場所)
方式
Credential Manager
接続名(oid)GitHub トークンrefresh
(接続なし)
裏側(今どこにどのトークンがあるか)
ブラウザ
アプリ(+SDK)
API Management
GitHub
Entra トークン(身分証)GitHub トークン(鍵)
トレース(何が起きたか)
  1. まだ何も起きていません。左でログインして「自分の Issue」を押してください。
監査ログ(API Management が記録したもの)
  • (まだ無い)
同じ listIssues() でも、方式 1 で if 文を外すと他人の Issue が出て、方式 2 では何をしても本人が見られる範囲しか出ない。その差を、上のデモで確かめてください。
6 / 鍵の所在

GitHub のトークンは、どこに無いか

「鍵を配らない」を確認する一番早い方法は、トークンが無い場所を数えることです。

GitHub トークンが無い場所 A さんのブラウザ アプリのコード アプリの環境変数 アプリのログ Cosmos DB リポジトリ・CI claude.ai / Claude Code(作る側) アプリが丸ごと漏れても、GitHub トークンは出てこない ある場所 API Management Credential Manager oid(A) → gho_…A refresh_…A oid(B) → gho_…B refresh_…B 保存時に暗号化(AES-128、鍵はデータごと) その鍵を Key Vault のマスター証明書で包む 取り出せるのはポリシーの実行時だけ GitHub の OAuth App の Client Secret もここ
左の7か所には Entra のトークン(身分証)しか通らない。GitHub を動かせる鍵は右の1か所にだけある。
アプリ開発者(と、アプリを書くエージェント)が知っているのは 「GitHub というコネクタがあり、listIssues() が呼べる」ことだけ。トークンの存在すら知らなくてよい。
7 / 補足

Microsoft 365 なら、同意画面すら要らない

GitHub は Entra と無関係なので「連携」が1回必要でした。SharePoint や Teams、Dataverse のように Entra で認証するシステムは、身分証をそのまま鍵に交換できます。これが On-Behalf-Of(OBO)です。

アプリ(+SDK) A さんの Entra トークン付き API Management JWT を検証したうえで 「A さんのトークンを持っています。 A さんとして Graph を呼ぶ券をください」 Entra ID 交換(OBO)。同意画面なし Graph 用の A さんトークン Microsoft Graph A さんの権限で応答 GitHub との違い: 連携ボタンも、トークンの保存も無い。身分証がそのまま鍵になる
Entra が両側(基盤と Graph)の認証を握っているので、その場で交換できる。GitHub のような外部サービスは Entra を知らないため、前章の「連携」が要る。
8 / まとめ

「どこで・何が・誰を」の一覧

場所確認すること使うもの落ちたら
Easy Auth(アプリの入口)この人は誰か。このアプリに入れる人かEntra ログイン、manifest の access グループアプリが開けない
アプリの中この人はこのアプリで何ができるかApp Roles(user.hasRole())ボタンが出ない・操作が拒否される
API Management(関所)このアプリはこのコネクタを呼んでよいか。この人の接続はあるかJWT 検証、manifest の承認、Credential Manager の接続(oid)GitHub に届く前に止まる。未連携なら連携ボタン
GitHubこの鍵の持ち主に何を見せてよいかA さん本人の GitHub トークン本人が見られない Issue は返らない
退職したら

Entra を無効にすれば、全アプリで同時に止まる

Entra トークンが発行されなくなるので、入口(Easy Auth)と関所(JWT 検証)の両方で落ちる。Credential Manager に残った GitHub トークンは使い手が消える。接続自体も削除する運用にしておく。

GitHub 側で権限が変わったら

次の呼び出しから、返る範囲が変わる

基盤側の設定は何も変えない。GitHub がトークンの持ち主で判定しているので、リポジトリから外された翌日には、その Issue は出てこない。方式 1 ではこれが起きない。

形式 A(単一 HTML)の場合

3 の矢印がブラウザから直接出る

サーバーコードが無いので、ブラウザ用 SDK が本人の Entra トークンを付けて API Management を呼ぶ。4 以降はまったく同じ。GitHub トークンがブラウザに来ないことも同じ。

1行の listIssues() の裏で、身分証は3回見られ(入口・関所・GitHub)、鍵は1回だけ、関所の中で使われる。人ごとに結果が違うのは、鍵が人数分あるからです。
出典
用語メモ / oid=Entra トークンに入っている、社員を一意に示す ID。Credential Manager の接続名にこれを使うと「この人の鍵」を引ける。Credential Manager=API Management の機能。外部サービスの OAuth 接続を保存・更新し、ポリシーから取り出せる。get-authorization-context=その取り出しを行うポリシー。refresh token=期限切れのアクセストークンを、同意画面なしで更新するための長寿命トークン。OBO(On-Behalf-Of)=Entra が、本人のトークンを別の API 用の本人トークンに交換するフロー。
← レポート一覧