まず症状
検索でここに来た人向けに、症状を先に置きます。同じならビンゴです。
- ローカルでは
az login --service-principalで 問題なくログインできる - 同じ App Registration を使っているはずの GitHub Actions ではこれが出る:
AADSTS700016: Application with identifier '<app-id>' was not found in the directory '<tenant-id>'. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.
- App Registration は Azure Portal で確認できる、確かに存在している
- Tenant ID も合っている(コピペで確認済み)
- Client ID も合っている(コピペで確認済み)
- なのに
Application ... was not foundと言われる
AADSTS700016 GitHub Actions azure/login Application not found Federated Credential subject 不一致 あたりで検索して来た人は、たぶんこれです。
原因はだいたい以下のどれかで、App Registration の存在問題ではないことがほとんどです:
- Federated Credential の subject が、実際に GitHub Actions が送ってくる subject と一致していない
- テナントを間違えている(個人テナントと組織テナントが混ざる)
- App ID と Object ID を混同している
- App Registration は残っているが Service Principal(Enterprise Application 側)が削除されている
- multi-tenant App で 対象テナントに consent していない
詳細は下に書きます。
構成
ローカル端末 │ az login --service-principal -u <app-id> -p <secret> --tenant <tenant-id> ▼ Entra ID │ ✅ ログイン成功 ▼ az group list ✅ 動く ────────────────────────────────────── GitHub Actions │ azure/login@v2 │ client-id / tenant-id / subscription-id (OIDC) ▼ Entra ID │ ❌ AADSTS700016 ▼ 何もできない
「同じ Client ID 使ってるはずなのに」が出発点。 ここで「同じ Client ID」という思い込みが、実は事故の核でした。
最初の勘違い
正直に書きます。最初に AADSTS700016 を見たとき、こう思いました。
「App ID 間違えたか、Tenant ID 間違えたかのどっちかでしょ」
これが全部間違ってた。両方合ってました。
何を勘違いしていたかを並べておきます。
勘違い1: 「同じ Client ID なら、ローカルでも CI でも同じ認証ができる」
違います。
ローカルの az login --service-principal は Client Secret を使う認証。
GitHub Actions の azure/login@v2 は OIDC 連携で Federated Credential を使う認証。
認証方式が違うので、Azure 側でも「別の経路で来てる」と扱われます。
そして OIDC 側には subject の照合という追加チェックがある。Client ID と Tenant ID が合ってても、subject が一致しなければ、Azure 側から見ると「その subject から認証可能なアプリが見つからない」状態になり、AADSTS700016 が返ってきます。
エラーメッセージが「Application not found」なので、つい App Registration 自体を疑ってしまうけど、真因は Federated Credential の subject 不一致ということが結構あります。
勘違い2: 「App ID = Object ID」
違います。これ Azure 経験者全員が一度はやらかすやつ。
- Application (Client) ID: App Registration を識別する ID。
az loginなどで使う - Object ID: Entra ID ディレクトリ内のオブジェクトを識別する ID。RBAC の
--assignee-object-idなどで使う - しかも App Registration の Object ID と Service Principal の Object ID はさらに別物
GitHub Actions のシークレットに「AZURE_CLIENT_ID」と書いてあったから Client ID を入れたつもりが、Portal でコピーしたのが Object ID だった、というやつ。コピペ元のボタン位置が紛らわしい。
勘違い3: 「App Registration があれば Service Principal もある」
違います。 App Registration(Entra ID のアプリの定義)と Service Principal(特定テナントでのインスタンス)は別物。
App Registration を削除してないけど、Enterprise Applications 側で Service Principal だけ消されてた、というケースが地味にあります。「テナント整理」をしたタイミングで、知らずに消されてることも。 この状態だと App Registration はあるのに「そのテナントにはそのアプリのインスタンスが無い」状態になり、AADSTS700016 が出ます。
勘違い4: 「Tenant ID は1つしかないでしょ」
違います。
個人で Microsoft アカウントを使ってる人は、個人テナント(xxxxxxxx.onmicrosoft.com)と、組織テナント(会社のテナント)の両方に所属していることがあります。
az account show で出てくる tenantId が、実は意図と違うテナントだった、ということが起きます。
特にハマるパターン:
- 会社の App Registration を使うつもりなのに、
az loginがデフォルトで個人テナントを選んでる - multi-tenant App を作って「どのテナントからでも使える」と思っていたが、対象テナントで一度も consent されてない
試したこと(時系列)
試行1: Client ID と Tenant ID を Portal から再コピペ
GitHub Actions のシークレットを全部消して、Portal から目視で一文字ずつ確認して入れ直す。
→ 変わらず AADSTS700016。
試行2: ローカルで再現を試みる
同じ Client ID と Tenant ID で、ローカルの az login --service-principal --tenant <id> -u <id> -p <secret> を実行。
→ 動く。
ここで「ローカルで動くなら、認証情報自体は合ってる」と思い込んでしまった。 実はこの思い込みが時間を溶かす元凶でした。ローカルは Secret 認証、CI は OIDC 認証で、別の経路だから別の確認が要る。
試行3: Tenant を疑って az account show
az account show --query "{tenantId:tenantId, user:user.name}"
→ Tenant ID は合ってた。
ここで完全に詰まりました。「全部合ってるのに何で?」が30分続くと、「もしかして Azure 側のバグでは?」という現実逃避が始まる。これも前回(ACI 編)と同じ症状で、だいたい自分のミスです。
試行4: Federated Credential の subject を確認
冷静になって、Portal で App Registration → Certificates & secrets → Federated credentials を開いて、設定されてる subject を確認しました。
設定値(イメージ):
repo:myorg/myrepo:ref:refs/heads/main
GitHub Actions が実際に送ってくる subject は、ワークフローのトリガーによって変わる:
| トリガー | subject |
|---|---|
main ブランチへの push |
repo:myorg/myrepo:ref:refs/heads/main |
develop ブランチへの push |
repo:myorg/myrepo:ref:refs/heads/develop |
| Pull Request | repo:myorg/myrepo:pull_request |
| Tag push | repo:myorg/myrepo:ref:refs/tags/v1.0.0 |
| Environment 指定あり | repo:myorg/myrepo:environment:production |
自分のワークフローは Pull Request トリガーだったのに、Federated Credential には refs/heads/main だけが登録されていた。
つまり Azure 側から見ると、「main ブランチからの push なら知ってる SPN だけど、PR から来た subject は知らない」という状態。だからアプリ自体は存在するのに 「そのアプリへの認証経路は無い」 → AADSTS700016 を返してきていた。
エラーメッセージが Application ... was not found なので App Registration を疑い続けてしまうけど、Azure としては嘘は言ってなくて、「この subject に対応する Federated Credential を持つアプリは無い」というのが真意でした。
本当の原因
GitHub Actions の OIDC 認証で AADSTS700016 が出る場合、ほとんどはこの3つのどれか:
- Federated Credential の subject が、実際のワークフロー実行時の subject と一致していない
- 必要なトリガーごとに Federated Credential を登録していない(main 用しか作ってないのに PR から実行してる、など)
- Environment を使っているのに、Federated Credential を branch ベースで作っている
修正方法:
Portal の App Registration → Certificates & secrets → Federated credentials で、ワークフローのトリガーごとに登録する。
PR でも動かしたいなら pull_request 用のエントリを追加。Environment 経由なら environment:production 用を追加。
Federated Credential 1: repo:myorg/myrepo:ref:refs/heads/main Federated Credential 2: repo:myorg/myrepo:pull_request Federated Credential 3: repo:myorg/myrepo:environment:production
GitHub Actions 側のワークフローで、id-token: write パーミッションも忘れずに:
permissions: id-token: write contents: read
これを抜くと subject が送られなくて別のエラーになる。
図で整理:「同じApp Registrationなのに、ローカルとCIで動作主体が違う」
これが今回の本質です。
┌──────────────────────────────────────────────────────────────┐ │ App Registration: my-app │ │ Client ID: xxxx-xxxx-xxxx │ │ │ │ ┌─ 認証手段A: Client Secret ──────────────────────┐ │ │ │ ローカル端末から az login で使う │ │ │ │ subject 照合なし、Secret さえ合えば通る │ │ │ └──────────────────────────────────────────────────┘ │ │ │ │ ┌─ 認証手段B: Federated Credential (OIDC) ────────┐ │ │ │ GitHub Actions / Terraform Cloud などから │ │ │ │ subject の完全一致が必要 │ │ │ │ ・repo:org/repo:ref:refs/heads/main │ │ │ │ ・repo:org/repo:pull_request │ │ │ │ ・repo:org/repo:environment:production │ │ │ │ ※ どれか1つでも未登録なら、そのトリガーは死ぬ │ │ │ └──────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────┘ 軸: 認証主体 = ローカルユーザーか / CI ジョブか / 別 SaaS か 認証手段 = Secret か / OIDC か / 証明書か Subject = OIDC では誰として来たか(branch / PR / environment) 対象 = どのテナントの、どのアプリに対して
「同じ App Registration を使ってる」と思っていても、認証手段ごとに別の経路があり、それぞれに別の登録が要る。 ローカルで Secret が通っても、それは「Secret 経路は生きてる」というだけで、「OIDC 経路も生きてる」ことの証明にはならない。
同じ構造の他サービス
「認証主体のズレ」は GitHub Actions 固有じゃないです。Azure に外から認証する場面はだいたい同じ構造で、サービスごとに subject の形式が違うのが余計にハマる原因。
| 環境 | 認証手段 | subject の形式(例) | よくある事故 |
|---|---|---|---|
| ローカル端末 | Client Secret / 証明書 | (subject 照合なし) | Tenant 間違い、Secret 期限切れ |
| GitHub Actions | OIDC + Federated Credential | repo:org/repo:ref:refs/heads/main |
branch / PR / environment ごとに登録漏れ |
| Azure DevOps | Service Connection (Workload Identity) | sc://org/project/connection-name |
Service Connection 再作成で subject 変わる |
| Terraform Cloud | OIDC + Federated Credential | organization:org:project:proj:workspace:ws:run_phase:plan |
plan/apply で別 subject、両方登録が要る |
| GitLab CI | OIDC + Federated Credential | project_path:group/proj:ref_type:branch:ref:main |
プロジェクトパス変更で subject 変わる |
特に注意したいのが Terraform Cloud。 plan と apply で送られてくる subject が違うので、片方しか登録してないと「terraform plan は通るのに apply で死ぬ」という事故が起きます。これは初見で必ずやらかすやつ。
つまり Azure に外部から認証する全ての経路で、「subject に何が入ってくるか」を毎回明示的に確認するのが安全です。デフォルトで察してくれません。
Azureの「認証主体ズレ」を切り分ける順番(テンプレ)
このフローは AADSTS700016 以外にも、AADSTS7000215(Secret 期限切れ)や AADSTS50020(テナント不一致)など、AAD 系エラー全般で使えます。
① 「ローカルで動くのに CI で動かない」と「CI で動くのにローカルで動かない」を区別する
これが最初の分岐。
- ローカルで動く → Secret 経路は生きてる。OIDC 経路を疑う
- CI で動く → OIDC 経路は生きてる。ローカル側の Secret / Tenant を疑う
- 両方動かない → App Registration / SPN そのものを疑う
② エラーコードを原文で確認する
| コード | 意味 |
|---|---|
AADSTS700016 |
App が見つからない(or その subject 経路が無い) |
AADSTS7000215 |
Secret が間違っている / 期限切れ |
AADSTS50020 |
テナント不一致(multi-tenant 関連が多い) |
AADSTS70011 |
scope が不正 |
AADSTS90002 |
テナントが存在しない / Tenant ID 間違い |
「Application not found」というメッセージで App Registration を疑い続けるのは罠。エラーコードで判断する。
③ ID の種類を毎回ラベル付きで確認する
Application (Client) ID : xxxx-xxxx-... Object ID (App Reg) : yyyy-yyyy-... ← 別物 Object ID (Service Princ): zzzz-zzzz-... ← さらに別物 Tenant ID : aaaa-aaaa-... Subscription ID : bbbb-bbbb-...
Portal でコピーボタンを押すたびに、何の ID をコピーしたかをラベル付きで確認する。 「とりあえずコピペ」で半日溶けます。
④ 「今、誰として実行しているか」を毎回 az account show で確認する
az account show --query "{tenantId:tenantId, user:user.name, type:user.type}"
user.type が servicePrincipal か user かで全然意味が違う。
個人 Microsoft アカウントで個人テナントに繋がってる、というのが地味によくある事故。
⑤ OIDC の場合、Federated Credential の subject を確認する
GitHub Actions では、azure/login のログや OIDC 関連のデバッグ出力から、実際に使われる subject を確認できる場合があります:
Run details: ... Subject: repo:myorg/myrepo:pull_request
これと Portal の Federated Credential 登録値を 目で diff する。 1文字でも違えば AADSTS700016。
⑥ App Registration と Service Principal の両方が存在することを確認する
# App Registration の確認
az ad app show --id <client-id> --query "{appId:appId, displayName:displayName}"
# Service Principal の確認(同じ Client ID で)
az ad sp show --id <client-id> --query "{appId:appId, objectId:id}"
App Registration はあるけど SP が消されてる、というケースはこれで一発で分かる。
⑦ multi-tenant App なら、対象テナントで consent されてるか確認する
az ad sp list --filter "appId eq '<client-id>'" --query "[].{tenant:appOwnerOrganizationId, displayName:displayName}"
対象テナントに SP が無ければ、admin consent が必要。
次回どう防ぐか(チェックリスト)
GitHub Actions × Azure OIDC で、最初に確認するリスト:
- [ ] Federated Credential を「ワークフローのトリガーごと」に登録(main / PR / tag / environment)
- [ ] ワークフローに
permissions: id-token: writeを書いた - [ ]
azure/login@v2のclient-idtenant-idsubscription-idは Portal からラベル確認しながらコピー - [ ] App ID と Object ID を混同していない
- [ ]
az ad sp show --id <client-id>で SP の存在を確認した - [ ] エラー出たら、コード(
AADSTS700016等)で判断する。メッセージ本文に騙されない - [ ] ワークフローログで「実際に送られた subject」を確認する
- [ ] ローカルで動いても OIDC 経路の保証にはならないことを覚えておく
AIに聞いた結果
ハマってる最中、Claude にも他の AI にも聞きました。
返ってきた答え:
- 「App Registration の Client ID と Tenant ID を確認してください」
- 「Service Principal が存在するか確認してください」
- 「Secret の有効期限を確認してください」
全部合ってる。全部合ってるんだけど、今回の真因(subject 不一致)にはなかなか辿り着かない。
これも前回の ACI 編と同じで、Web 上の解説記事の多くが「Client ID / Tenant ID / Secret を確認」までしか書いていないので、AI もその平均値を返してきます。 Federated Credential の subject 一致は公式ドキュメントには書いてあるけど、解説記事の表面には出てきにくい一行。
最終的に解決したのは、ワークフローログで実際に送られた subject を見て、Portal の登録値と目で diff した瞬間でした。
AI は「ありがちな原因リスト」は得意。 でも「自分の状況に固有の subject 文字列が違う」を当てるのは苦手。
これはたぶん構造的にそうで、ログを見ないと当てられない種類のエラーは、人間がログを読みに行くしかない。 AI に依頼するときは「実際に送られた subject はこれです、Portal の登録値はこれです」までこちらが揃えて渡せば、当ててくれる確率は跳ね上がります。
おわりに:シリーズの軸として
前回(ACI × ACR 編)の結論はこうでした:
Azure の 401 / 403 はほぼ全部、Identity / Scope / Timing / Plane の4軸に分解できる
今回の AADSTS700016 は、その中の Identity(誰) をさらに分解した話です。
「同じ App Registration を使ってる」と思っていても、認証手段ごとに別の主体として扱われる。 ローカル Secret の自分と、GitHub Actions OIDC の自分は、Azure から見ると別人。
「でも昨日までは動いてた」が始まったら危険です。 Azure 認証事故は、だいたい『今、自分が誰として実行しているか』の認識がズレているときに起きます。 CLI で成功した時点で「認証情報そのものは合ってる」と思い込み始めるけど、Azure では「どこから・どの手段で実行したか」で別主体として扱われる。
このシリーズで一貫して書きたいのは、
Azure は『誰が・何をしてるか』を明示しないと死ぬ
という一点です。同じ沼に落ちた人の役に立ったら嬉しいです。