Microsoft Graph API createUploadSession の uploadUrl で 401 Unauthorized が発生したときの原因と対処

2025年4月中旬以降、Microsoft Graph API の createUploadSession を使ったメール添付ファイルアップロードで、安定稼働していたコードが突然 401 Unauthorized を返すという事例が世界的に報告されました。本記事では、この事象の原因となったサービスインシデント EX1058997 と技術的背景、実務で取るべき対処・設計ベストプラクティスを、開発者目線で整理します。

目次

Microsoft Graph API createUploadSession で何が起きたのか

まずは現象を整理します。問題となったのは、Outlook / Exchange Online のメールに大きな添付ファイルを付けるために利用される、Microsoft Graph API の大容量アップロード機能です。

  • 対象エンドポイント:
    POST /v1.0/me/messages/{id}/attachments/createUploadSession
    POST /v1.0/users/{userId}/messages/{id}/attachments/createUploadSession
  • 症状:
    1. createUploadSession を呼び出すと、uploadUrl を含むレスポンスは正常に返る
    2. 返却された uploadUrl に対して PUT でチャンクを送信すると、401 Unauthorized が返ってくる
    3. コードは数年にわたり安定動作しており、2025年4月16日頃を境に急に失敗し始めた
  • 影響範囲:
    一部テナントのみで発生(約10社が影響を受ける一方、約200社は問題なしという報告)。

この時点で、開発者側のコード変更が一切ないにもかかわらずエラーが発生していることから、「サーバー側(Microsoft 365 / Exchange Online)の回帰バグを疑うべき状況」と判断できます。

正常パターンと障害パターンの比較

実際に発生した事例を抽象化すると、次のような違いが見られます。

項目正常時障害時
createUploadSession のレスポンスuploadUrl が返る(200 OK)同様に uploadUrl は返る(200 OK)
uploadUrl への PUT201 / 202 など成功コード全テナントまたは一部テナントで 401 Unauthorized
uploadUrl のクエリauthtoken を含むauthtoken が欠落しているケースが確認された
コード変更なしなし(にもかかわらず突然失敗)

ポイントは、「uploadUrl 自体は返っているが、その中に含まれるべき認証情報(authtoken)が欠落していた」という点です。

Microsoft のサービスインシデント EX1058997

この問題は、Microsoft によって Exchange Online のサービスインシデント EX1058997 として正式に認識・公開されました。

インシデントの概要は次の通りです。

  • 事象:Microsoft Graph API の createUploadSession を利用した場合に、メール添付のアップロード(特に 2 MB 超)に失敗する
  • 影響:Exchange Online 上での添付ファイルアップロード(Graph 経由)が 401 となり、クライアント実装に依存せず同様のエラーが再現
  • 根本原因:Exchange Online 側の更新により、Graph API の createUploadSession が使用する内部認証設定に不具合が入り、有効な OAuth 2.0 トークンがバックエンドに正しく渡らなくなった

Microsoft 側のタイムラインと開発者のアクション

時期(UTC / EST ベース)Microsoft 側の対応開発者が取るべき行動
2025-04-18 02:00 EST ごろサービスインシデント EX1058997 として認識 Graph API フロントエンドから Exchange Online(EXO)バックエンドへの内部呼び出しで、OAuth 2.0 トークンが渡らないバグ を修正 修正のロールアウトを開始アプリ側のコード変更は不要 失敗時は指数バックオフ付きでリトライ 業務影響が大きい場合は、時間を空けて再試行する運用を一時的に採用
2025-04-21 前後修正がグローバルに展開され、新規の uploadUrl に正しい authtoken が含まれるようになる 多くのテナントで 401 エラーが解消したことがユーザーコミュニティからも報告された通常運用に復帰 古い uploadUrl を保持しているジョブがあれば、セッションを再生成して新しい URL を使う
2025-04-24 最終更新問題の原因となった更新をロールバック 一連の監視を経て「影響は解消された」と公式にアナウンス監視ログとアラートルールを見直し、同種インシデントに備える 障害時の影響範囲とビジネスインパクトを社内でレビュー
2025-06 以降(散発)一部ユーザーから「再び同じ 401 が発生している」との報告が継続 2025年10月時点でも sporadic に同様の質問が上がっている再発を疑う場合は、Microsoft 365 管理センター > サービス正常性 を最優先で確認 再現条件・ログを添えて早期にサポートチケットを起票

これらの情報は、Microsoft Q&A や各種ステータスページに記載されたインシデント情報から確認できます。

技術的背景:Graph フロントエンドと EXO バックエンド間の認証不備

では、なぜ uploadUrl に対する PUT が 401 になったのでしょうか。内部的なリクエストフローを簡略化すると次のようになります。

1. クライアント
   ↓ (Authorization: Bearer <access_token>)
2. Microsoft Graph API (フロントエンド)
   ↓ (内部的に Exchange Online へ委譲)
3. Exchange Online (メールボックス・添付ファイル保存)

createUploadSession のレスポンスに含まれる uploadUrl は、実質的には「Exchange Online に対する事前認可付き URL(署名付き URL のようなもの)」です。この URL には、Graph と EXO 間の認証情報として authtoken というクエリパラメータが含まれます。

  • 正常時:
    • uploadUrl 内の authtoken によって、EXO への PUT が正しく認証される
    • クライアント側は通常、Authorization ヘッダーを付けなくても(あるいは付けても)問題なくアップロードできる
  • 障害時:
    • Graph → EXO 間で有効な OAuth 2.0 トークンが正しく渡らないバグが発生
    • その結果、生成される uploadUrl に authtoken が含まれない、または無効な値になる
    • EXO は認証に失敗し、401 Unauthorized を返却

つまり、クライアント側コードは正しくても、「サーバーが渡すべき内部トークンが欠落しているために 401 が発生した」 という構図です。

uploadUrl の良い例・悪い例

実際の値は機密情報なのでここではパターンのみ示します。

種類例ポイント
正常な uploadUrlhttps://outlook.office.com/api/.../attachments/..../content?authtoken=eyJ0eXAiOiJKV1QiLCJhbGci...&...authtoken= にトークンが含まれており、これが EXO 側での認証に使われる
障害時の uploadUrlhttps://outlook.office.com/api/.../attachments/..../content?something=...&...authtoken パラメータ自体が欠落、または値が異常であることが確認されている

トラブルシューティングの際には、ログに残しておいた uploadUrl(マスク済み)からクエリ部分を確認し、authtoken の有無をチェックすることが非常に有効です。

自分の環境でこのインシデントに当たっているか確認するポイント

すでに発生が収束していても、今後の再発や類似インシデントに備えて、切り分けの観点を整理しておきましょう。

観点確認内容判断の目安
エラー発生箇所createUploadSession が 200 を返しているか? 401 が返っているのは uploadUrl への PUT だけか?createUploadSession から 401/403 なら認可設定の問題の可能性が高い uploadUrl への PUT だけ 401 なら、今回のようなバックエンド側障害の可能性
テナント間比較同一実装で、テナント A では成功、テナント B では失敗していないか? 影響テナント数と正常テナント数をざっくり把握一部テナントのみで再現する場合、Microsoft 側のローリング展開/構成差異が疑われる
uploadUrl の内容ログから uploadUrl を取り出し、クエリパラメータを確認 authtoken が存在するかどうかをチェックauthtoken が無い/明らかにフォーマットがおかしい場合はサーバー側生成の問題を強く疑う
ファイルサイズ小さな添付(< 2MB)では問題ないが、大きな添付のみエラーになっていないか?EX1058997 では「2 MB 超の添付」で顕在化したと報告されているため、サイズ依存の切り分けも重要

推奨ベストプラクティス:エラーハンドリングとリトライ戦略

今回のような「クラウドサービスの内部障害」に対して、アプリケーション側でできることは限られますが、適切なリトライとセッション再生成 を組み込んでおくことで業務影響を大きく和らげることができます。

ステータスコード別 リトライの目安

HTTP ステータス推奨アクションポイント
429 / 503 / 504必ずリトライ(指数バックオフ+Retry-After ヘッダー尊重)一時的な負荷・制限系。Graph のドキュメントでもリトライ推奨。
500 / 502 / 503(再発)リトライ+createUploadSession の再生成サーバー内部エラーが継続する場合、セッション自体の再作成を検討。
401 / 403まずはアクセストークンの有効性・スコープをチェック それでも解消しない場合は、createUploadSession の再生成+数回リトライ今回のようなサービス障害時にも、「一時的な権限エラー」として扱いリトライする価値がある。
4xx(400 / 404 / 413 など)多くはクライアント側の問題。ログに残して即座に失敗とするただし 404 が一時的なレプリケーション遅延で出るケースもあるので、要件次第で限定的なリトライを検討。

C# による簡易リトライ実装例

以下は、uploadUrl への PUT 時に 401/5xx を一定回数までリトライし、必要に応じて createUploadSession を再生成するイメージコードです(実運用ではロギングやキャンセルトークンを追加してください)。

public async Task UploadLargeAttachmentAsync(
    GraphServiceClient graph,
    string messageId,
    Stream fileStream,
    string fileName,
    CancellationToken ct)
{
    // 1. createUploadSession
    var uploadSession = await graph.Me.Messages[messageId]
        .Attachments
        .CreateUploadSession(new CreateUploadSessionPostRequestBody
        {
            AttachmentItem = new AttachmentItem
            {
                AttachmentType = AttachmentType.File,
                Name = fileName,
                Size = fileStream.Length
            }
        })
        .PostAsync(ct);

    var uploader = new LargeFileUploadTask&lt;FileAttachment&gt;(
        uploadSession, fileStream, maxSliceSize: 320 * 1024);

    int attempt = 0;
    const int maxAttempts = 5;

    while (true)
    {
        try
        {
            var result = await uploader.UploadAsync(ct);
            if (result.UploadSucceeded)
            {
                return;
            }

            throw new Exception("Upload failed without exception.");
        }
        catch (ServiceException ex) when (IsRetryableStatus(ex.StatusCode))
        {
            attempt++;
            if (attempt &gt; maxAttempts)
            {
                throw;
            }

            await Task.Delay(ComputeBackoff(attempt), ct);

            // 必要に応じて、uploadSession の再生成も検討
            if (NeedRecreateSession(ex.StatusCode))
            {
                uploadSession = await graph.Me.Messages[messageId]
                    .Attachments
                    .CreateUploadSession(new CreateUploadSessionPostRequestBody
                    {
                        AttachmentItem = new AttachmentItem
                        {
                            AttachmentType = AttachmentType.File,
                            Name = fileName,
                            Size = fileStream.Length
                        }
                    })
                    .PostAsync(ct);

                uploader = new LargeFileUploadTask&lt;FileAttachment&gt;(
                    uploadSession, fileStream, maxSliceSize: 320 * 1024);
            }
        }
    }
}

ここで重要なのは、「何でもかんでも無限リトライ」ではなく、リトライ対象ステータスと回数を明確に決めることです。サービス障害時でも、アプリケーションを暴走させずに「一定時間であきらめてフォールバックに切り替える」ことができます。

サービス正常性の監視をシステム設計に組み込む

クラウドサービスを前提にしたシステムでは、「自分のコードのモニタリング」だけでなく「サービス側のヘルスチェック」も設計の一部として組み込むべきです。

  • Microsoft 365 管理センターの「サービス正常性」ダッシュボードで、Exchange Online(EXO)や Microsoft 365 suite のインシデントを確認
  • 障害 ID(今回であれば EX1058997)の検出をトリガーに、社内向けステータスページやアラートを自動更新
  • 障害発生中は、大容量添付機能を UI 上で「一時停止」表示するなど、ユーザーへのフィードバックを変える

これにより、ユーザーから「送れないんだけど?」と問い合わせが来る前に、「現在 Microsoft 365 側で障害が発生しており、再試行をお願いしています」 と proactively に案内できるようになります。

ログ設計:uploadUrl と authtoken の扱い

今回の事象から得られる教訓のひとつは、「サーバーが返す URL の中身を、プライバシーとセキュリティに配慮しつつログに残しておく」ことの重要性です。

ログに残したい情報

  • createUploadSession のレスポンス概要(ステータスコード、HTTP ヘッダの一部)
  • uploadUrl のドメイン名とパス、クエリキーのみ(値はマスク)
  • PUT 時のステータスコードとレスポンスヘッダ
  • テナント ID / ユーザー ID(可能なら匿名化)
  • ファイルサイズ、チャンクサイズ、試行回数などのメタ情報

クエリパラメータのマスク例(C#)

public static string SanitizeUploadUrl(string rawUrl)
{
    var uri = new Uri(rawUrl);
    var query = HttpUtility.ParseQueryString(uri.Query);

    foreach (var key in query.AllKeys)
    {
        if (key == null) continue;
        // authtoken など機密度の高いパラメータはマスク
        if (key.Equals("authtoken", StringComparison.OrdinalIgnoreCase) ||
            key.StartsWith("token", StringComparison.OrdinalIgnoreCase))
        {
            query[key] = "***";
        }
    }

    var builder = new UriBuilder(uri)
    {
        Query = query.ToString()
    };

    // 例: https://outlook.office.com/api/.../content?authtoken=***
    return builder.Uri.ToString();
}

このようにしておけば、障害発生時に「authtoken がそもそも無かったのか」「値がおかしかったのか」といった切り分けができる一方、トークンの漏えいリスクも抑えられます。

フォールバック案:添付ファイルをあきらめないために

大容量添付ファイルのアップロードは、多くの業務システムでクリティカルな機能です。Graph API が一時的に利用できない場合に備え、ビジネス継続のためのフォールバックパス を設計しておくことをおすすめします。

代表的なフォールバック戦略

  • 一時的に SMTP 経由でメール送信
    • 添付ファイルをメールに直付けせず、OneDrive / SharePoint などにアップロード
    • メール本文には共有リンクのみを埋め込む
    • Graph API 側の添付アップロードが復旧したら元の経路に戻す
  • 業務フローを「リンク前提」に切り替える
    • 平常時から「ファイルは常にクラウドストレージに置き、メールはリンクのみ」というポリシーに寄せておく
    • 大容量添付が必須となるシナリオを減らしておくことで、今回のような障害の影響を最小化
  • ユーザー向け UI での明示的な通知
    • 添付アップロードに失敗したら、「現在 Microsoft 365 側で障害が発生している可能性があります」とメッセージ
    • 代替手段(共有リンクで送る、一定時間後に自動再試行するなど)を提示

フォールバックを実装しておくことで、「添付が付かないままメールだけ送られてしまう」「ユーザーが何度も同じ操作を繰り返す」といった UX 上の問題も防げます。

再発時にやるべきことチェックリスト

今後、EX1058997 と同様の障害が再発した場合に備え、運用チーム向けのチェックリストをまとめておきます。

  1. サービス正常性を確認
    • Microsoft 365 管理センターのサービス正常性で、Exchange Online / Microsoft Graph / Microsoft 365 suite のインシデント有無を確認
    • 関連しそうなインシデント ID を控える(EX○○○形式)
  2. 影響範囲を把握
    • 全テナントか、一部テナントか
    • 全ユーザーか、一部ユーザーか
    • 全ファイルサイズか、2MB 以上のみか
  3. 技術的な切り分け
    • createUploadSession のレスポンスは成功しているか
    • uploadUrl に対する PUT のステータスとレスポンスボディを確認
    • ログから uploadUrl のクエリを確認し、authtoken の有無をチェック
  4. サポートチケットの起票
    • 再現手順・タイムスタンプ・対象テナント・request-id / client-request-id などをまとめて Microsoft サポートに提出
    • 可能であれば Graph Explorer での再現結果も添える
  5. フォールバックの有効化
    • 大容量添付を OneDrive / SharePoint の共有リンクに切り替える
    • 障害が収束するまでの間、添付付き送信を一時停止する設定も検討

よくある勘違い・アンチパターン

最後に、今回のような障害に直面した際にやってしまいがちな「よくない対応」を挙げておきます。

  • 認証エラーを無視して無限リトライ
    • 401/403 を「とりあえずリトライしておけばそのうち通るだろう」と無制限ループする
    • 結果としてサービスへの負荷を高め、スロットリングやさらなる障害を招く
  • uploadUrl に自前のトークンを付け足す
    • Authorization ヘッダーやクエリに独自のトークンを追加して何とかしようとする
    • Graph / EXO 内部の署名付き URL の仕様に反しており、セキュリティリスクにもつながる
  • authtoken を長期保存して再利用
    • 障害時の調査目的でなく「後で楽をするため」に authtoken を永続的に保存する
    • 有効期限付きのトークンを再利用する設計は、セキュリティ上も信頼性上もアンチパターン
  • ユーザーに何も知らせない
    • バックエンドでエラーになっているのに、フロントではスピナーだけが回り続ける UI
    • 「今何が起きているか」「いつ頃復旧見込みか」を最低限でも伝えるべき

まとめ:クラウドサービス前提の設計に何を学ぶか

Microsoft Graph API の createUploadSession から返される uploadUrl に対する PUT が、突然 401 Unauthorized を返すようになった今回のインシデントは、純粋に「自分たちのコードのバグ」ではありませんでした。Graph フロントエンドと Exchange Online バックエンド間の認証バグ(EX1058997)という、クラウドサービス側の問題だったのです。

しかし、だからといって開発者側に何もできないわけではありません。

  • リトライ戦略や createUploadSession の再生成など、堅牢なエラーハンドリング を仕込んでおく
  • サービス正常性ダッシュボードと連携し、障害情報を自動的に検知・通知 する
  • uploadUrl のマスクログなど、原因切り分けに必要な情報だけを安全に記録 する
  • SMTP+クラウドストレージリンクといった フォールバック経路 を用意し、業務を止めない

こうした工夫を積み重ねることで、「クラウドサービスは落ちることがある」ことを前提にしつつも、ユーザー体験と業務継続性を高いレベルで維持することができます。

もしあなたのシステムが Microsoft Graph API を使って大容量の添付ファイルを扱っているなら、今回の EX1058997 を「過去の一件」として流さず、自社システムの設計と運用プロセスを見直す絶好の機会として活用してみてください。

この記事を書いた人

実務の現場で詰まりがちなポイントを地図にするITブログ「IT trip」を運営。Windows/Office(Teams・Excel)からSQL、サーバ運用、ガジェットまで、再現性のある手順と“なぜそうなるか”を丁寧に解説します。読んだらすぐ試せること、そして迷った人の次の一歩が見えることを大切にしています。

コメント

コメントする

目次