アプリにOutlook Calendar APIを統合する方法

公開日

前回の記事「Google Calendar API をアプリケーションに統合する方法」では、開発者が Google Calendar をアプリに接続するための手順を一つずつ解説しました。

本記事では、同じ流れを Outlook Calendar API で行います。Azure でのアプリ登録、スコープ設定、検証、よくある統合時の注意点、そして実際のコード例を順に見ていきます。

前提条件

このガイドでは、Azure Active Directory(現在の名称は Microsoft Entra ID)にアクセスできる Microsoft の仕事用アカウントまたは開発者アカウントをすでにお持ちであることを前提としています。

ディレクトリの外でアプリケーションを作成することはできなくなりました。まだディレクトリをお持ちでない場合は、Microsoft 365 Developer Program に参加するか、Azure にサインアップしてください。

Outlook Calendar API をアプリに統合する手順

ステップ 1: Microsoft Azure ポータルにサインインする

Microsoft Azure Portal サインインページを開き、お持ちのアカウントでサインインしてください。

Azure ポータルのページ

Sign in to the Microsoft Azure Portal

ステップ 2: 新しいアプリケーションを登録する

Microsoft Azure Portal にサインインしたら、以下を実行します。

  1. Azure Active Directory に移動 Azure App Directory Home
  2. 検索バーで 「App Registrations」 を検索 Search App Registrations
  3. 「App Registrations」 をクリック Click App Registrations
  4. 「New registration」 をクリック Click New Registration
  5. 必要事項を入力 Azure Active Directory - Register the application
    1. Name : アプリケーション名を入力します。同意画面でユーザーに表示される名前です。
    2. Supported account types : 開発するアプリの種類(社内利用かマルチテナントか)に応じて選択します。ここでは 「Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)」 を選択します。これにより、Outlook.com ユーザーを含む任意の組織のユーザーがアプリを利用できます。SaaS 製品やその他のマルチテナント アプリに最適な選択肢です。
    3. Redirect URI : 任意項目です。アプリの種類によっては不要ですが、Web アプリの場合はほぼ必須です。Azure が OAuth レスポンスを送る先になるためです。「Select a platform」で Web を選び、Redirect URI(例: https://yourapp.com/auth/callback )を入力します。入力したドメインが到達可能で、自分の管理下にあることを確認してください。
    4. 「Register」 をクリック
    5. 「Register」をクリックすると Azure Active Directory がアプリを作成し、概要ページに移動します。ここで Application (client) IDDirectory (tenant) ID を控えてください。Application (client) ID はアプリを識別する GUID です。Directory (tenant) ID は常に必要なわけではなく、マルチテナント アプリでは通常 common エンドポイントを使いますが、自分のテナントでのテストには便利です。 Application Created

ステップ 3: API の権限を設定する

アプリを登録したら、次はカレンダー権限の設定です。これらの権限は OAuth フローの中で表示されるため、ユーザーはカレンダーへのアクセスを許可する前に、アプリが要求するスコープを確認できます。

既定では User.Read 権限だけが付与されています。サインインとユーザープロファイルの取得だけが目的であれば、このステップはスキップできます。

API Permissions を追加する手順は次のとおりです。

  1. 左側の 「Manage」 タブをクリック
  2. 「API Permissions」 をクリック Click API Permissions
  3. 「+ Add a permission」 をクリック Click _Add a permission_ button
  4. Microsoft Graph カードを選択(「Add a permission」をクリックすると右側に開くパネルの、通常は最初のカードです) Click the _Microsoft Graph_ card
  5. Delegated permissionsApplication permissions から選択します。サインイン中のユーザーの代わりに API を呼び出す場合は Delegated permissions、サインインしたユーザーなしでバックグラウンド サービスとして動かす場合は Application permissions を使います。この例では Delegated permissions を使います。 Click _Delegated Permissions_
  6. 「Calendars」で検索し、アプリに本当に必要な権限だけを選択します。多くの場合は Calendars.ReadWrite と、共有カレンダーが必要なら Calendars.ReadWrite.Shared です。カレンダーのスコープはユーザー委任型なので通常は管理者の同意は不要ですが、ユーザーの同意を制限している組織もあります。外部テナントのユーザーが同意できない場合は、そのテナントの管理者がアプリに同意する必要があります。 Search for _Calendars Permissions_

ステップ 4: ID トークンを有効にする

この手順はすべてのアプリに必要なわけではありませんが、ユーザーの名前、メールアドレス、プロフィール画像 URL などの情報にアクセスしたい場合は、Manage -> Authentication にある ID Tokens オプションを有効にする必要があります。

ID トークンがあれば、アプリは追加の API 呼び出しをせずに、カレンダー接続の直後にユーザーを識別できます。

Enable ID Tokens option

ステップ 5: クライアント シークレットを生成する

カレンダーの読み取り、書き込み、更新などの操作はすべてサーバー側で行うことを推奨しています。そのためにクライアント シークレットが必要です。

クライアント シークレットを生成する手順は次のとおりです。

  1. 「Certificates & secrets」 タブをクリック Click the “Certificates & secrets tab”
  2. 「New client secret」 をクリック Click “New client secret”
  3. 説明と有効期限を入力 Enter a description and an expiration date

クライアント シークレットを生成したら、すぐにコピーして安全な場所(通常は .env ファイル)に保管してください。

ステップ 6: ブランディングと検証

Branding & Properties セクションで、ロゴや説明、利用規約 URL などを設定できます。これは任意ですが、同意画面をきちんと見せるために推奨します。パブリッシャー ドメイン(通常は Azure AD で検証済みの独自ドメイン)を設定することも重要です。設定しないと、同意時にアプリが「未検証」と表示されます。マルチテナント アプリでは、Microsoft は広く利用するためにパブリッシャー検証を求めるようになっています。未検証の場合、2020 年 11 月に導入されたセキュリティ ポリシーにより、他テナントのユーザーが同意できないことがあります。

Branding & Properties screen

ステップ 7: Outlook Graph API に慣れる

アプリの設定が終わったら、Microsoft Calendar Graph API を確認して、イベントの作成・更新・削除に使うエンドポイントに慣れておきましょう。

ステップ 8: すべてのカレンダー プロバイダーを単一 API で統合できるサービスの検討

Microsoft Graph API のドキュメントは比較的充実していますが、それでも Unified Calendar API のような製品を検討することをおすすめします。すべてのカレンダー プロバイダーを 1 つの API で統合できるからです。

Unified Calendar API を使えば、アプリに実装する API は 1 つだけで、各プロバイダーの制限や API の違いを気にせずにすべてのプロバイダーに対応できます。

もう 1 つの利点は、複数のプロバイダー連携を保守したり、破壊的変更に対応したり、思いもよらないエッジ ケースを処理したりする必要がなくなることです。

Apiroc Unified Calendar API

Outlook Calendar 認可フローの例

以下のフローチャートは、ユーザーが Outlook Calendar をアプリに接続するための、シンプルな OAuth フローを示したものです。

Outlook OAuth 2.0 Flow

Microsoft OAuth2 フローについて詳しく知りたい場合は、Microsoft OAuth2 フローのドキュメント ページ を参照してください。

クライアント側 (UI)

const microsoftOauthUrl = getMicrosoftOAuthUrl()
<button href="microsoftOauthUrl"   rel="noopener noreferrer"> Connect Outlook Calendar </button>
export const SCOPES = [
  "openid",
  "email",
  "profile",
  "offline_access",
  "Calendars.ReadWrite",
  "User.Read",
];

export interface ClientState {
  session: Session;
  returnUrl?: string;
}

export function stateToB64(session: ClientState): string {
  return encode(JSON.stringify(session));
}

export function getMicrosoftOAuthUrl(
  state: ClientState,
) {
  const nonce = uuid();
  const TENANT_ID = process.env.NEXT_PUBLIC_MICROSOFT_TENANT_ID;
  const params = new URLSearchParams({
    client_id: process.env.MICROSOFT_CLIENT_ID || "",
    redirect_uri: `${getHostName()}/api/connect/microsoft`,
    response_type: "code id_token",
    scope: SCOPES.join(" "),
    prompt: "consent",
    response_mode: "form_post",
    state: stateToB64(state),
    nonce,
  });
  return `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/authorize?${params}`;
}
  • prompt パラメータには loginnoneconsentselect_account の 4 つの値のいずれかを指定します。
  • response_mode には queryfragmentform_post のいずれかを指定します。ここでは form_post を選び、Microsoft からサーバー側のリダイレクト URI へ POST リクエストが送られるようにしています。

API 側 (バックエンド)

次に API ハンドラーを作成します。Microsoft のサーバーからコードとスコープを受け取り、トークンに交換する役割です。

この例ではバリデーションに zod を使っています。

const successSchema = z.object({
  code: z.string(),
  state: z.string(),
  id_token: z.string(),
  session_state: z.string().optional(),
});

const errorSchema = z.object({
  error: z.string(),
  error_description: z.string().optional(),
});
type ErrorParams = z.infer<typeof errorSchema>;

const querySchema = z.union([successSchema, errorSchema]);

function isError(query: Record<string, any>): query is ErrorParams {
  return Boolean(query.error);
}

const microsoftHandler: NextApiHandler = async (req, res) => {
  try {
    const result = querySchema.parse(req.body);
    if (isError(result)) {
      const q = new URLSearchParams({
        error: "ACCESS_DENIED",
        provider: CalendarProvider.MICROSOFT,
      });

      console.error({ result });
      return res.redirect(302, `/?${q}`);
    }

    const { session, returnUrl } = stateFromB64(result.state);
    const { email } = decodeIdToken(result.id_token);
    const { access_token, refresh_token, expires_in, scope } =
      await exchangeCodeForTokens(result.code);

    const connection = await upsertConnection(
      {
        email,
        accessToken: access_token,
        refreshToken: refresh_token,
        expiresInSeconds: expires_in,
        status: ConnectionStatus.ACTIVE,
        provider: CalendarProvider.MICROSOFT,
        scopes: scope,
      },
      session.user
    );

    const q = new URLSearchParams({
      cid: connection.id,
    });
    if (returnUrl) q.append("returnUrl", returnUrl);

    res.redirect(302, returnUrl ? returnUrl : `/calendars/microsoft?${q}`);
  } catch (e: any) {
    let error = JSON.stringify(e);

    const querystr =
      typeof req.query === "string" ? req.query : JSON.stringify(req.query);

    const q = new URLSearchParams({
      error,
      provider: CalendarProvider.MICROSOFT,
    });
    return res.redirect(302, `/?${q}`);
  }
};

export default microsoftHandler;

クライアント側と同様に、コードをトークンに交換する処理などは小さなユーティリティ関数にしておくことを推奨します。

const TENANT_ID = process.env.MICROSOFT_TENANT_ID;

export async function exchangeCodeForTokens(code: string) {
  const data = new FormData();
  data.append("client_id", process.env.MICROSOFT_CLIENT_ID || "");
  data.append("scope", SCOPES.join(" "));
  data.append("code", code);
  data.append("redirect_uri", `${getHostName()}/api/connect/microsoft`);
  data.append("grant_type", "authorization_code");
  data.append("client_secret", process.env.MICROSOFT_CLIENT_SECRET || "");

  try {
    const result = await fetch(
      `https://login.microsoftonline.com/${TENANT_ID}/oauth2/v2.0/token`,
      {
        method: "POST",
        body: data,
      }
    );

    const json = await result.json();
    if (json.error) throw json;

    const parsed = responseSchema.parse(json);
    return parsed;
  } catch (e) {
    console.error("Exchange failed");
    throw e;
  }
}

Outlook Calendar API 統合時の注意点

  • 検証には時間がかかり、ときにもどかしいこともある : Outlook 上で開発する開発者は数千人、あるいは数百万人にのぼるため、Microsoft は毎日大量のアプリ申請を審査しています。申請時にはすべての項目をきちんと入力し、ロードマップに検証期間を組み込んでおきましょう。

  • 本当に必要なスコープだけを要求する : Microsoft のチームはアプリを細かく審査するため、機能に本当に必要な権限だけをリクエストしてください。承認が通りやすくなるだけでなく、ユーザーがアクセスを許可する際にも役立ちます。実際の機能と関係のないスコープを要求されると、ユーザーは混乱してしまいます。

  • Webhook は期限切れになるため、必ず更新する : カレンダーの変更を監視するために Webhook を登録する場合は、数時間ごとに実行され、期限切れが近いサブスクリプションをすべて更新するバックグラウンド ジョブを用意してください。

  • レート制限とスロットリング : Microsoft にはレート制限とスロットリングの厳格なルールがあります。項目を 1 件ずつ取得するのを避け、アプリ側の API 呼び出しにレート制御を入れて、いわゆる「MailboxConcurrency」エラーを回避しましょう。以下の表にレート制限とスロットリングの制限をまとめます。

    対象制限備考
    メールボックスごと(アプリ ID とメールボックスの組み合わせ)10 分あたり 10,000 リクエスト同時 4 リクエストいわゆる「MailboxConcurrency」エラーです。
    アップロードメールボックスあたり 5 分間で合計 150 MB の PATCH/POST/PUT大きな ICS ファイルや添付ファイルを付けると到達することがあります。
    Global Graph全テナント合計でアプリあたり 10 秒あたり 130,000 リクエストまれですが、大規模な SaaS のバックフィルで発生することがあります。
    リトライの作法429503/504 では Retry-After ヘッダーを確認し、指数バックオフで待つ毎秒叩き続けると Graph API はスロットリングを続けます。
  • タイムゾーンの落とし穴 : Outlook ではユーザーがタイムゾーン名を手入力できます(この先の展開は想像がつくでしょう)。このケースもコードで処理できるようにしておいてください。

  • 同意と権限の問題 : スコープの節で説明したとおり、 Calendars.ReadWrite はユーザー委任型の権限です。多くのテナントはユーザーの同意を許可していますが、そうでないテナントもあります。 「admin consent required」 エラーに備え、ユーザーに管理者へ依頼してもらう分かりやすいフローを用意しましょう。

Apiroc Unified Calendar API で全カレンダープロバイダーをアプリに統合

カレンダー連携は私たちの得意分野です。私たちのチームは Google Calendar、Outlook、iCloud Calendar と長年向き合い、数十億回の API 呼び出しを処理するカレンダー連携を構築してきました。

主要なカレンダー API すべてから学んだ教訓は Apiroc Unified Calendar API に生かされています。開発者はカレンダー関連の問題に何百時間も費やすことなく、製品を前進させる機能の開発に集中できます。

サインアップして Apiroc の Unified Calendar API をお試しください。複数のカレンダープロバイダーを単一の API で統合できます。無料プランにクレジットカードは不要です。

よくある質問

Outlook カレンダー API を使う前に必要なアカウントは?

Azure Active Directory にアクセスできる Microsoft 仕事用または開発者アカウントが必要です。

カレンダーに完全アクセスするにはどの権限スコープを追加すべき?

Microsoft Graph で Calendars.ReadWrite(共有カレンダーが必要なら Calendars.ReadWrite.Shared も)を追加してください。

パブリッシャードメインとブランディングを設定する理由は?

パブリッシャーの検証とブランド付きの同意画面によって、アプリが「未検証」と表示されるのを防げます。現在はほとんどのマルチテナント アプリで求められています。

Outlook のウェブフックサブスクリプションはどれくらいの頻度で更新が必要?

Graph カレンダーの Webhook は数時間で失効します。期限切れが近いサブスクリプションを更新するバックグラウンド ジョブを用意しておきましょう。

Outlook カレンダー API はプッシュ通知をサポートしていますか?

はい。Microsoft Graph のサブスクリプションを作成すれば、ポーリングをしなくても変更通知を受け取れます。

Outlook、Google、iCloud カレンダーを簡単に統合する方法は?

はい。Apiroc のような Unified Calendar API を使えば、主要プロバイダーを 1 つの一貫した JSON インターフェースで扱えます。Apiroc Unified Calendar API なら、プロバイダーごとに個別の連携を開発・保守する必要がありません。