1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Microsoft 365の『アプリの登録』と『エンタープライズアプリ』の違いを整理して、サービス正常性をTeamsに通知してみた

1
Last updated at Posted at 2026-09-26

はじめに

Microsoft365のアプリを登録する機会がごくたまに発生するんですが、
そもそもアプリ登録とは?やアプリの登録とエンタープライズアプリケーションって何が違うんだっけ?と迷子になっているため理解を深めるために投稿します。

この記事では、アプリの登録の役割とエンタープライズアプリケーションとの違いを整理しつつ、実際にMirosoft365のサービス正常性をTeamsチャネルに通知する仕組みを作っています。

この記事でわかること

  • アプリの登録とは何か?
  • アプリの登録とエンタープライズアプリケーションの違い
  • アプリの登録〜APIアクセス許可〜管理者の同意までの手順
  • Pythonでサービス正常性を取得し、Teamsへ通知するサンプルコード

先に結論

  • アプリの登録=アプリの「設計図」(アプリケーションオブジェクト)を作る場所
  • エンタープライズアプリケーション = そのアプリがテナント内で実際に動くための「実体」(サービスプリンシパル)を管理する場所
  • 自分で作るスクリプトやツールからGraph APIを呼ぶなら、まず「アプリの登録」から作る

本記事での実行環境

項目 内容
OS macOS
Python 3.12(Homebrewでインストール)
ライブラリ msal / requests / python-dotenv
権限 Entra IDでアプリ登録と管理者の同意ができる管理者アカウント
通知先 Teamsチャネル(Workflowsアプリのwebhook)

1. Microsoft 365のアプリケーション登録とは

アプリの登録とは、自作のアプリやスクリプトをEntra ID(旧Azure AD)に「このアプリは信頼できる存在です」と登録する作業です。

Graph APIを呼ぶには「どのアプリが」「どの権限で」アクセスするのかをEntra IDが把握している必要があり、その身分証を発行する手続きがアプリの登録です。

アプリを登録すると、主に次の情報が得られます。

項目 役割
アプリケーション(クライアント)ID アプリを一意に識別するID
ディレクトリ(テナント)ID アプリが所属するテナントのID
資格情報(クライアントシークレット/証明書) アプリが本人であることを証明するためのパスワード相当のもの
APIのアクセス許可 アプリが何にアクセスしてよいか(例:ServiceHealth.Read.All)

この3点セット(テナントID・クライアントID・シークレット)を使ってアクセストークンを取得し、Graph APIを呼び出す、というのが基本の流れになります。


2. アプリの登録とエンタープライズアプリケーションの違い

Entra管理センターでアプリを登録すると、アプリの登録にもエンタープライズアプリケーションにそれぞれオブジェクトが作られます。

アプリの登録 エンタープライズアプリケーション
実体 アプリケーションオブジェクト サービスプリンシパル
たとえると アプリの「設計図」 テナントに置かれた「実体(インスタンス)」
存在する場所 アプリを作ったテナント(ホームテナント)に1つだけ アプリを使う各テナントに1つずつ
主に設定すること リダイレクトURI、資格情報、要求するAPIのアクセス許可など ユーザー割り当て、SSO設定、同意済みの権限の確認、サインインログなど
代表的な例 自作スクリプト、社内ツール 自作アプリの実体+外部SaaS(Zoom、Salesforceなど)

ポイントは次の2つです。

  1. アプリの登録をすると、自テナントのエンタープライズアプリケーションにも自動で同じ名前のアプリが現れる
    → 設計図(アプリケーションオブジェクト)と実体(サービスプリンシパル)がセットで作られるためです。
  2. 外部SaaSを追加した場合は、エンタープライズアプリケーションにだけ現れる
    → 設計図は提供元ベンダーのテナントにあり、自テナントには実体だけが作られるためです。
  • 自分でアプリを作る → 「アプリの登録」から作成する。アプリの設計図と実体が必要になる。
  • 既存のアプリ(Zoomなど)を「誰が使えるか」「どうサインインするか」を管理する → 「エンタープライズアプリケーション」で設定する。設計図は提供元ベンダーに登録されている。

3. 実際にアプリケーション登録を実施してみる

ここからは実践編です。サービス正常性の状態をTeamsに通知するアプリを作っていきます。

全体の構成

[Pythonスクリプト]
   │ ① クライアントID+シークレットでトークン取得
   ▼
[Entra ID] ──トークン──▶ [Pythonスクリプト]
   │ ② トークンを付けてGraph APIを呼び出し
   ▼
[Microsoft Graph:サービス正常性API]
   │ ③ 異常のあるサービスだけ抽出
   ▼
[Teams Workflows(webhook)] ──▶ Teamsチャネルに投稿

手順1:アプリを登録する

  1. Microsoft Entra管理センター にサインイン
  2. 「Entra ID」→「アプリの登録」→「新規登録」を選択
  3. 次のとおり入力して「登録」をクリック
項目 設定値
名前 service-health-teams(わかりやすい名前でOK)
サポートされているアカウントの種類 この組織ディレクトリのみに含まれるアカウント(シングルテナント)
リダイレクトURI 空欄(ユーザーのサインインを伴わないため不要)

アプリケーションの新規登録画面

アプリケーションの登録後の画面

※上記の概要ページに表示される アプリケーション(クライアント)ID と ディレクトリ(テナント)ID を控えておく

手順2:クライアントシークレットを発行する

  1. 登録したアプリの「証明書とシークレット」→「新しいクライアントシークレット」
  2. 説明と有効期限を入力して「追加」
  3. 表示された 「値」 を控える

⚠️ シークレットの値は作成直後の一度しか表示されません。画面を離れる前に必ず控えましょう。
また、有効期限が切れると通知も止まるので、期限はカレンダー等に登録しておくと安心です。

💡 そもそもクライアントシークレットとは?

クライアントシークレットは、アプリが「自分は登録されたあのアプリ本人です」と証明するためのパスワードです。
人がサインインするときは「ユーザー名+パスワード」を使いますが、アプリの場合は 「クライアントID(ユーザー名に相当)+クライアントシークレット(パスワードに相当)」 を使ってEntra IDに認証し、アクセストークンを受け取ります。

今回のように人がサインインせずにスクリプトが自動で動く場合(クライアント資格情報フロー)、このシークレットがアプリの身元を証明する唯一の手段になります。
そのため、シークレットが漏れると、第三者がそのアプリになりすましてGraph APIを呼び出せてしまいます。取り扱いには次の点に気をつけましょう。

  • コードに直書きせず、.env や Azure Key Vault などで管理する
  • GitHubなどに誤ってプッシュしない(.gitignore に追加する)
  • 有効期限を設け、期限が来る前に更新する

なお、アプリの資格情報にはシークレットの代わりに 証明書 も使えます。本番運用では、よりセキュアな証明書の利用が推奨されています。今回は手軽に試すためシークレットを使います。

手順3:APIのアクセス許可を追加して、管理者の同意を与える

  1. 「APIのアクセス許可」→「アクセス許可の追加」→「Microsoft Graph」
  2. 「アプリケーションの許可」 を選択
  3. ServiceHealth.Read.All を検索してチェックし、「アクセス許可の追加」
  4. 「(テナント名)に管理者の同意を与えます」をクリック
  5. 状態が「✅ 付与されました」になっていることを確認
    ここでのポイントは 「委任されたアクセス許可」ではなく「アプリケーションの許可」を選ぶことです。

アクセス許可で管理者の同意が付与されましたの表示

種類 動き方 今回の用途
委任されたアクセス許可 サインインしたユーザーの代わりにアクセスする × 人がサインインしない
アプリケーションの許可 アプリ自身の権限でアクセスする ○ スクリプトが無人で定期実行する

手順4:Teams側でwebhookを用意する

Teamsの通知先として、Workflows(Power Automate) のwebhookを使います。

  1. 通知したいTeamsチャネルの「…」→「ワークフロー」
  2. テンプレート「Webhook要求を受信するとチャネルに投稿する」を選択
  3. 投稿先のチームとチャネルを確認して作成
  4. 表示された webhookのURL を控える

手順5:Pythonで通知スクリプトを作る

環境構築

mkdir -p ~/projects/service-health-teams && cd ~/projects/service-health-teams
python3.12 -m venv .venv
source .venv/bin/activate
pip install msal requests python-dotenv

.env(認証情報はコードに直書きしない)

TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
CLIENT_SECRET=xxxxxxxxxxxxxxxxxxxxxxxx
TEAMS_WEBHOOK_URL=https://xxxxx.logic.azure.com/workflows/...

.env は必ず .gitignore に追加しておきましょう。

main.py

import os
 
import msal
import requests
from dotenv import load_dotenv
 
load_dotenv()
 
TENANT_ID = os.environ["TENANT_ID"]
CLIENT_ID = os.environ["CLIENT_ID"]
CLIENT_SECRET = os.environ["CLIENT_SECRET"]
TEAMS_WEBHOOK_URL = os.environ["TEAMS_WEBHOOK_URL"]
 
HEALTH_URL = "https://graph.microsoft.com/v1.0/admin/serviceAnnouncement/healthOverviews"
 
# 通知しないステータス(正常・復旧済み・誤検知など)
IGNORE_STATUSES = {
    "serviceOperational",
    "serviceRestored",
    "falsePositive",
    "postIncidentReviewPublished",
}
 
# Graph APIのステータス値 → Teamsに表示する日本語
STATUS_LABELS = {
    "investigating": "調査中です",
    "serviceDegradation": "一部で問題が発生しています(性能低下)",
    "serviceInterruption": "サービスが中断しています",
    "restoringService": "サービスを復旧中です",
    "extendedRecovery": "復旧に時間がかかっています",
    "verifyingService": "復旧を確認中です",
    "investigationSuspended": "調査を中断しました",
}
 
 
def get_access_token() -> str:
    """クライアントID+シークレットでアクセストークンを取得する"""
    app = msal.ConfidentialClientApplication(
        CLIENT_ID,
        authority=f"https://login.microsoftonline.com/{TENANT_ID}",
        client_credential=CLIENT_SECRET,
    )
    result = app.acquire_token_for_client(
        scopes=["https://graph.microsoft.com/.default"]
    )
    if "access_token" not in result:
        raise RuntimeError(f"トークン取得に失敗しました: {result.get('error_description')}")
    return result["access_token"]
 
 
def get_unhealthy_services(token: str) -> list[dict]:
    """サービス正常性を取得し、現在問題が発生しているサービスだけ返す"""
    res = requests.get(
        HEALTH_URL,
        headers={"Authorization": f"Bearer {token}"},
        timeout=30,
    )
    res.raise_for_status()
    services = res.json()["value"]
    return [s for s in services if s["status"] not in IGNORE_STATUSES]
 
 
def build_teams_payload(services: list[dict]) -> dict:
    """Teamsに投稿するアダプティブカードを組み立てる"""
    # 「- **サービス名**: 状態」の箇条書きを作る(未知のステータスは英語のまま表示)
    lines = [
        f"- **{s['service']}**: {STATUS_LABELS.get(s['status'], s['status'])}"
        for s in services
    ]
    return {
        "type": "message",
        "attachments": [
            {
                "contentType": "application/vnd.microsoft.card.adaptive",
                "content": {
                    "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
                    "type": "AdaptiveCard",
                    "version": "1.4",
                    "body": [
                        {
                            "type": "TextBlock",
                            "text": "サービス正常性通知",
                            "weight": "Bolder",
                        },
                        {
                            "type": "TextBlock",
                            "text": "⚠️ 以下のサービスで問題が発生しています:",
                            "wrap": True,
                        },
                        {
                            "type": "TextBlock",
                            "text": "\n".join(lines),
                            "wrap": True,
                        },
                    ],
                },
            }
        ],
    }
 
 
def main() -> None:
    token = get_access_token()
    services = get_unhealthy_services(token)
 
    if not services:
        print("すべてのサービスが正常です。通知はスキップします。")
        return
 
    res = requests.post(TEAMS_WEBHOOK_URL, json=build_teams_payload(services), timeout=30)
    res.raise_for_status()
    print(f"{len(services)}件をTeamsに通知しました。")
 
 
if __name__ == "__main__":
    main()

実行してみる

python main.py

異常のあるサービスがあれば、次のようなカードがTeamsチャネルに投稿されます。

image.png

すべて正常な場合は、コンソールに「すべてのサービスが正常です。」と表示され、通知はスキップされます。

つまずきポイント

症状 原因と対処
403 Forbidden が返る 管理者の同意が未付与、または「委任されたアクセス許可」で追加している → 手順3を再確認
トークン取得で invalid_client シークレットの「値」ではなく「シークレットID」を設定している、または期限切れ
Teamsに投稿されない webhook URLの誤り、またはカードのJSON形式の誤り → ワークフローの実行履歴を確認

まとめ

  • アプリの登録はアプリの「設計図」、エンタープライズアプリケーションはテナント内の「実体」
  • 自作ツールからGraph APIを使うなら「アプリの登録」から作る
  • 無人で動くスクリプトには 「アプリケーションの許可」+管理者の同意 が必要
  • テナントID・クライアントID・シークレットの3点セットでトークンを取得し、Graph APIを呼び出す
  • 今回は手動実行でしたが、定期実行の仕組みや、障害の詳細(インシデント情報)まで通知する方法に挑戦したいと思います。

参考資料

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?