アプリにOutlook Calendar APIを統合する方法
- 著者
- 名前
- Eraldo Forgoli
- 公開日
目次
すべてのカレンダーを単一の API で統合する
単一の API であらゆるカレンダー プロバイダーをアプリに統合できる Unified Calendar API へのアクセスを得るために、Unified Calendar API のウェイトリストにご参加ください。
前回の記事「Google Calendar API をアプリケーションに統合する方法」では、Google Calendar API をアプリケーションに統合するために開発者が従うべきすべての手順を解説しました。
本記事では、Azure でのアプリ登録、スコープ設定、検証、統合時の注意点、実際のユースケースを含め、Outlook Calendar API をアプリに統合する方法を説明します。
前提条件
このガイドでは、すでに Azure Active Directory へアクセス可能な Microsoft の Work アカウントまたは Developer アカウントをお持ちであることを前提としています。
ディレクトリ外でアプリケーションを作成する機能は廃止されています。ディレクトリをお持ちでない場合は、Microsoft 365 Developer Program に参加するか、Azure にサインアップしてください。
Outlook Calendar API をアプリに統合する手順
Step 1: Microsoft Azure ポータルにサインインする
Microsoft Azure Portal サインインページにアクセスし、サインインしてください。
Azure ポータルのページ
Step 2: 新しいアプリケーションを登録する
Microsoft Azure Portal にサインインしたら、以下を実行します。
Azure Active Directory に移動
検索バーで 「App Registrations」 を検索
「App Registrations」 をクリック
「New registration」 をクリック
必要事項を入力
Name: ユーザーに表示されるアプリケーション名を入力します。
Supported account types: アプリの種類に応じて選択します(社内利用かマルチテナントか)。ここでは 「Accounts in any organizational directory (Any Microsoft Entra ID tenant - Multitenant)」 を選択します。これにより、Outlook.com ユーザーを含む任意の組織のユーザーがアプリを利用できます。
Redirect URI: 任意。Web アプリの場合は OAuth レスポンスを受け取る URI(例:
https://yourapp.com/auth/callback
)を入力します。「Register」 をクリック
登録後に表示される Application (client) ID と Directory (tenant) ID を控えてください。
Client ID と Tenant ID は安全な場所に保存し、通常は環境変数として管理してください。
Step 3: API の権限とクライアント シークレットを設定する
アプリ登録後、カレンダー権限を設定します。権限は OAuth フローで表示され、ユーザーは承認前にアプリのスコープを確認できます。
既定では User.Read 権限が付与されています。ユーザープロファイルの取得のみであればこのステップをスキップできます。
API Permissions を追加する手順
左側の 「Manage」 タブをクリック
「API Permissions」 をクリック
「+ Add a permission」 をクリック
Microsoft Graph カードを選択
Delegated permissions と Application permissions から選択
「Calendars」で検索し、必要な権限(例: Calendars.ReadWrite, Calendars.ReadWrite.Shared)を選択
Step 4: クライアント シークレットを生成する
カレンダー操作はサーバー側で行うことを推奨するため、クライアント シークレットを生成します。
「Certificates & secrets」 タブをクリック
「New client secret」 をクリック
説明と有効期限を入力
生成後はシークレットを再表示できません。必ずコピーして安全な場所(例: .env
)に保管してください。
Step 5: ブランディングと検証
Branding & Properties セクションで、ロゴや説明、利用規約 URL などを設定できます。これは任意ですが、承認画面を洗練させるために推奨されます。マルチテナント アプリでは publisher-verified が必須となり、未検証の場合は他テナントのユーザーが承認できないことがあります。
Step 6: Outlook Graph API に慣れる
アプリを設定したら、Microsoft Calendar Graph API を確認し、イベントの作成・更新・削除などのエンドポイントに慣れておきましょう。
Step 7: すべてのカレンダー プロバイダーを単一 API で統合できるサービスの検討
Microsoft Graph API は十分にドキュメント化されていますが、Unified Calendar API を利用すれば、すべてのカレンダー プロバイダーを 1 つの API で統合できます。これにより、複数のプロバイダーごとの違いや制限を気にせずに開発できます。
OneCal Unified Calendar API
Outlook Calendar 認可フローの例
以下のフローチャートは、ユーザーが Outlook Calendar をアプリに接続するシンプルな OAuth フローを示しています。
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
パラメータはlogin
none
consent
select_account
のいずれかです。response_mode
はquery
fragment
form_post
のいずれかです。ここではform_post
を使用し、Microsoft からリダイレクト URI へ POST されるようにしています。
API 側 (バックエンド)
次に、Outlook からコードとスコープを受け取りトークンに交換する API ハンドラーを作成します。ここでは 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,
reminderCount: 0,
lastRemindedAt: null,
},
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 統合時の注意点
検証には時間がかかることがある
Microsoft Teams は日々多数のアプリ申請を処理しています。申請時にはすべての詳細を入力し、ロードマップで検証期間を考慮してください。必要最小限のスコープのみ要求する
不要な権限は申請を難しくし、ユーザーの混乱を招きます。必要な権限だけをリクエストしましょう。Webhook は期限切れになるため更新が必要
カレンダー変更を監視する場合、数時間ごとに期限切れ前の購読を更新するバッチジョブを実装してください。レート制限とスロットリング
Microsoft には厳格な制限があります。一括取得を避けアプリ内でレート制御を行い、いわゆる「MailboxConcurrency」エラーを回避します。
スコープ (Scope) | 制限 (Limit) | 備考 (Notes) |
---|---|---|
メールボックスごと<br>(アプリ ID & メールボックス) | 10,000 requests / 10 min 同時 4 requests | いわゆる “MailboxConcurrency” エラー |
アップロード | 150 MB 合計 PATCH/POST/PUT / 5 min メールボックスあたり | 大きな ICS や添付ファイルで発生 |
Global Graph | 130,000 requests / 10 s アプリ全体 | 大規模 SaaS のバックフィルで稀に発生 |
リトライ方針 | 429 や 503/504 で Retry-After を確認し指数的バックオフ | 連続リクエストでは Graph API は引き続きスロットリング |
タイムゾーンの落とし穴
Outlook ではユーザーが任意のタイムゾーン文字列を入力できるため、想定外のフォーマットに備えてください。同意 & 権限の問題
Calendars.ReadWrite
はユーザーデリゲートですが、テナントによっては管理者の同意が必要です。「管理者の承認が必要」 エラーを検出し、ユーザーに管理者へ依頼するフローを案内しましょう。
OneCal Unified Calendar API で全カレンダープロバイダーを統合
OneCal は 2022 年以来、主要カレンダープロバイダー間で数十億件のイベントを同期してきました。私たちの経験から生まれた OneCal Unified Calendar API により、開発者はカレンダーに関する課題に何百時間も費やすことなく、機能開発や成長に集中できます。
Unified Calendar API 待機リストに参加して、リリース時に通知を受け取り、早期価格と割引をお得にご利用ください。