Microsoft Teams カレンダー連携ガイド -- Entra ID アプリ登録から Publisher Verification まで

Microsoft Teams カレンダー連携ガイド -- Entra ID アプリ登録から Publisher Verification まで

はじめに

Microsoft Teams(Outlook)のカレンダーとアプリを連携させるには、Microsoft Entra ID(旧 Azure AD)でアプリを登録して、Microsoft Graph API 経由でアクセスする。

Google の GCP 設定と似たような流れだが、Microsoft 独自の概念がいくつかある。「Supported account types」の選び方、Delegated permissions と Application permissions の違い、そしてシークレットに最大24ヶ月の有効期限があるという点は、最初に知っておかないとあとで痛い目を見る。

この記事では、Entra ID でのアプリ登録から API の叩き方、本番公開に必要な Publisher Verification までをまとめた。Google Calendar API との違いも随所で比較している。

全体の流れ

  1. Microsoft Entra ID でアプリ登録
  2. API のアクセス許可を追加
  3. クライアントシークレットの作成
  4. リダイレクト URI の設定
  5. API リクエストとレスポンス
  6. (本番公開時)Publisher Verification

1. Microsoft Entra ID でアプリ登録

  1. Microsoft Entra admin center にアクセス
  2. 左メニュー → 「Identity」→「Applications」→「App registrations」
  3. New registration」をクリック
  4. 以下を入力:
項目説明
Name任意のアプリ名
Supported account types対象ユーザーに応じて選択(下記参照)
Redirect URI後で設定するので空のまま
  1. Register」をクリック
  2. 登録後の画面で以下をコピーして安全な場所に保管:
    • Application (client) ID
    • Directory (tenant) ID

Supported account types の選択肢

意味
Accounts in any organizational directory and personal Microsoft accounts組織 + 個人アカウント両方(最も広い)
Accounts in any organizational directory組織アカウントのみ(個人 Microsoft アカウント不可)
Accounts in this organizational directory only自テナントのみ(自社限定)
Personal Microsoft accounts only個人アカウントのみ

2. API のアクセス許可を追加

  1. 左メニュー → 「API permissions
  2. Add a permission」→「Microsoft Graph」→「Delegated permissions
  3. 以下のスコープを追加:
スコープ用途
openidOpenID Connect 認証
User.Readユーザーのプロフィール・メールアドレス取得
Calendars.ReadWriteカレンダーの予定の読み書き
offline_accessリフレッシュトークンの取得(バックグラウンド同期に必要)
  1. Add permissions」をクリック

注意: Calendars.ReadWrite は管理者の同意が不要な Delegated permission。ユーザーが自分のデータへのアクセスを許可するだけで使える。


3. クライアントシークレットの作成

  1. 左メニュー → 「Certificates & secrets
  2. 「Client secrets」タブ → 「New client secret
  3. 以下を入力:
項目説明
Description任意のラベル
Expires推奨: 24 months
  1. Add」をクリック
  2. 表示される Value(シークレット値)をコピーして安全な場所に保管

注意: シークレット値は作成直後しか表示されません。コピーし忘れたら再作成が必要です。

注意: 有効期限が切れると連携が停止します。期限が近づいたらシークレットを再作成してください。


4. リダイレクト URI の設定

  1. 左メニュー → 「Authentication
  2. Add a platform」→「Web
  3. 認証後の戻り先 URL を追加:
環境
ローカル開発http://localhost:8080/api/auth/callback/microsoft
本番https://your-app.example.com/api/auth/callback/microsoft
  1. Configure」をクリック

追加設定(同じ Authentication ページ)

項目推奨値
Front-channel logout URL空のまま
Implicit grant and hybrid flowsすべてオフ(Authorization Code Flow を使用)
Allow public client flowsNo

5. API リクエストとレスポンス

ベース URL:

https://graph.microsoft.com/v1.0

すべてのリクエストに以下のヘッダーが必要:

Authorization: Bearer {アクセストークン}
Content-Type: application/json

5-1. 予定一覧の取得

GET /me/calendar/events?$top=250&$filter=start/dateTime ge '2026-01-01T00:00:00' and end/dateTime le '2026-12-31T23:59:59'&$orderby=start/dateTime HTTP/1.1

レスポンス(HTTP 200)

{
  "value": [
    {
      "id": "AAMkADI2...",
      "subject": "会議",
      "bodyPreview": "週次定例ミーティング",
      "body": { "contentType": "html", "content": "<p>週次定例ミーティング</p>" },
      "start": { "dateTime": "2026-04-07T06:30:00.0000000", "timeZone": "UTC" },
      "end": { "dateTime": "2026-04-07T07:30:00.0000000", "timeZone": "UTC" },
      "isAllDay": false,
      "location": { "displayName": "会議室A" },
      "organizer": { "emailAddress": { "name": "山田太郎", "address": "user@outlook.com" } },
      "isOnlineMeeting": true,
      "onlineMeetingProvider": "teamsForBusiness",
      "onlineMeeting": { "joinUrl": "https://teams.microsoft.com/l/meetup-join/..." },
      "showAs": "busy",
      "importance": "normal",
      "sensitivity": "normal",
      "iCalUId": "040000008200...",
      "changeKey": "RlFKaE...",
      "createdDateTime": "2026-03-17T06:43:11Z",
      "lastModifiedDateTime": "2026-03-17T06:43:11Z",
      "webLink": "https://outlook.live.com/owa/?itemid=..."
    }
  ],
  "@odata.nextLink": "https://graph.microsoft.com/v1.0/me/calendar/events?$skip=250"
}

レスポンスフィールド一覧

フィールド説明
idイベントの一意識別子。更新・削除に使用
subjectタイトル
body.content本文(HTML 形式)
bodyPreview本文のプレビュー(プレーンテキスト)
start.dateTime開始日時(UTC)。終日イベントの場合は 0000-00-00T00:00:00.0000000 形式
end.dateTime終了日時(UTC)
start.timeZone / end.timeZoneタイムゾーン(通常 UTC で返る)
isAllDay終日イベントかどうか
location.displayName場所
locations複数の場所(配列)
organizer.emailAddress主催者のメールアドレスと表示名
attendees参加者リスト(emailAddress, type, status を含む配列)
isOnlineMeetingTeams 会議かどうか
onlineMeetingProvider会議プロバイダー(teamsForBusiness / skypeForBusiness 等)
onlineMeeting.joinUrlTeams 会議の参加リンク
showAsfree / tentative / busy / oof(外出中)/ workingElsewhere / unknown
importancelow / normal / high
sensitivitynormal / personal / private / confidential
iCalUIdiCalendar 形式の一意識別子
changeKeyバージョン番号。変更検知に使用
isReminderOnリマインダーが有効か
reminderMinutesBeforeStartリマインダーの分数
hasAttachments添付ファイルがあるか
recurrence繰り返しルール(pattern + range オブジェクト)
isCancelledキャンセルされたか
responseStatus自分の出欠状態(response: accepted / tentativelyAccepted / declined 等)
categoriesカテゴリ(色タグ)の配列
createdDateTime作成日時(UTC)
lastModifiedDateTime最終更新日時(UTC)
webLinkOutlook 上でこの予定を開く URL
@odata.nextLink次ページがある場合の URL。このURLをそのまま GET して次ページを取得

5-2. 予定の作成

POST /me/calendar/events HTTP/1.1

リクエストボディ

{
  "subject": "新しい予定",
  "body": { "contentType": "text", "content": "テスト予定" },
  "start": { "dateTime": "2026-04-10T10:00:00", "timeZone": "Asia/Tokyo" },
  "end": { "dateTime": "2026-04-10T11:00:00", "timeZone": "Asia/Tokyo" },
  "location": { "displayName": "会議室B" },
  "isReminderOn": false
}

レスポンス(HTTP 201 Created)

作成されたイベントオブジェクトが返る(5-1 のフィールドと同じ構造)。id が新規に発行される。


5-3. 予定の更新

PATCH /me/calendar/events/{eventId} HTTP/1.1

リクエストボディ(変更したいフィールドのみ)

{
  "subject": "会議(変更後)",
  "start": { "dateTime": "2026-04-07T16:00:00", "timeZone": "Asia/Tokyo" },
  "end": { "dateTime": "2026-04-07T17:00:00", "timeZone": "Asia/Tokyo" }
}

レスポンス(HTTP 200)

更新後のイベントオブジェクトが返る。


5-4. 予定の削除

Microsoft Graph API では通常の DELETE と完全削除(permanentDelete)がある。

DELETE /me/events/{eventId} HTTP/1.1
  • HTTP 204 No Content — 削除成功(ゴミ箱に移動)
POST /me/events/{eventId}/permanentDelete HTTP/1.1
  • HTTP 204 No Content — 完全削除成功(ゴミ箱にも残らない)

Google Calendar API との違い

項目GoogleMicrosoft
タイトルsummarysubject
説明文description(プレーンテキスト)body.content(HTML 対応)
場所location(文字列)location.displayName(オブジェクト)
日時のタイムゾーンレスポンスにそのまま含まれる通常 UTC で返る(Prefer: outlook.timezone ヘッダーで変更可)
会議リンクhangoutLinkonlineMeeting.joinUrl
公開設定visibility (public/private)sensitivity (normal/private/confidential)
予定の空き表示transparency (opaque/transparent)showAs (busy/free/tentative/oof)
ページネーションnextPageToken パラメータ@odata.nextLink URL をそのまま GET
削除DELETE → 即削除DELETE → ゴミ箱 / permanentDelete → 完全削除
繰り返しRRULE 文字列pattern + range オブジェクト

6. 本番公開時: Publisher Verification

一般公開するには Publisher Verification(発行者の確認)が推奨。未確認だと同意画面に「未確認の発行者」警告が出る。

前提条件

  • Microsoft Cloud Partner Program (MPN) に登録済み(partner.microsoft.com
  • MPN アカウントのドメインが Entra テナントのドメインと一致していること
  • アプリ登録の publisher domain が設定済み

手順

  1. Entra admin center → App registrations → アプリ名
  2. 左メニュー → 「Branding & properties」
  3. Publisher domain」を確認済みドメインに設定
  4. Publisher verification」→「Verify
  5. MPN ID を入力 → 「Verify and save
  6. 同意画面に青いバッジが表示されれば完了

管理者の同意が必要な場合

組織によっては IT 管理者の承認が必要。管理者向けの同意 URL:

https://login.microsoftonline.com/{tenant-id}/adminconsent?client_id={client-id}

API スロットリング制限

制限上限適用範囲
リクエスト数10,000 / 10分app x mailbox
同時リクエスト4app x mailbox

他プロバイダーとの比較

項目GoogleMicrosoft TeamsAppleLINE WORKS
認証方式OAuth 2.0OAuth 2.0CalDAV Basic 認証OAuth 2.0 / JWT
開発者登録GCP プロジェクトEntra ID アプリ登録不要Developer Console
Client ID / Secret必要必要不要必要
本番審査OAuth Verification (3-5営業日)Publisher Verification不要不要
API 形式REST (JSON)REST (JSON)CalDAV (XML/ICS)REST (JSON)
シークレット有効期限なし最大24ヶ月 (要更新)なしなし

注意事項

Client ID・Client Secret は平文でコードに埋め込まないこと。環境変数などで管理する。

Entra ID のシークレットは最大24ヶ月で失効する。失効するとすべてのユーザーの連携が停止するため、期限管理を忘れないこと。


トラブルシューティング

症状原因と対処
リダイレクト URI が一致しない (AADSTS50011)Entra の Authentication に登録した URI とアプリ側の設定が完全一致しているか確認(末尾の / に注意)
アプリが見つからない (AADSTS700016)Client ID が正しいか確認。Tenant ID の設定も確認
シークレットが無効 (AADSTS7000215)シークレットが期限切れか、値が正しくコピーされていない
管理者の承認が必要組織の IT 管理者がサードパーティアプリを制限している。管理者に承認を依頼
「未確認の発行者」警告Publisher Verification が完了していない(機能には影響しない)
認証後にトークンが取れないリフレッシュトークンが失効(最大90日)。ユーザーに再度ログインしてもらう

参考リンク


この記事に関連するサービス

TodoONada株式会社では、認証基盤・ID管理の設計から開発・移行までを支援しています。

導入・開発のご相談はお問い合わせからお気軽にどうぞ。検討段階のご相談も歓迎です。

技術ブログ一覧へ戻る