Node.jsでSharePointリスト取得時のID3035エラー解消ガイド

SharePoint環境におけるアプリ開発では、Node.jsを通じたデータ取得が大変便利です。しかし、トークンや権限の設定がうまくいかず、エラーを引き起こすことも少なくありません。本記事では、特に「ID3035」エラーの原因と対策を詳しく解説し、快適な開発を実現するためのヒントを提供します。

「ID3035: The request was not valid or is malformed.」エラーとは

SharePointからリストを取得しようとした際、Azure Active Directory (AAD) v2.0エンドポイントを用いてアクセストークンを取得し、Bearerトークンを付与してSharePoint REST API(例:https://<テナント名>.sharepoint.com/_api/web/lists)へアクセスすると、まれに「ID3035: The request was not valid or is malformed.」というエラーが返ってくることがあります。
このエラーは一見、リクエストが「不正にフォーマットされている」「何らかの必須情報が不足している」などを連想させます。しかし実際には、認証やトークンのスコープ設定、アプリの権限付与が原因になっているケースが多いのです。ここでは、なぜこのエラーが起こるのかを詳細に紐解き、適切に解決するための手順を解説します。

エラー発生の主な原因

1. トークン取得時のスコープ(resource)の誤り

Node.jsアプリからAzure ADのv2.0エンドポイントを用いてアクセストークンを取得する際、スコープを正しく指定しないとSharePoint REST APIが期待する権限をトークンに含められず、結果として「ID3035」のようなエラーが発生しやすくなります。
v2.0エンドポイントでは、一般的に「https://<テナント名>.sharepoint.com/.default」といった形で「/.default」を付加したスコープを設定する必要があります。もしGraph APIを利用するのであれば「https://graph.microsoft.com/.default」を指定しますが、SharePoint固有のREST APIを叩く場合はテナントごとのURLが必要になる点に注意してください。

2. アプリ登録と権限(Consent)の問題

Azureポータルの「アプリの登録」で適切な権限を設定していない、あるいは管理者による同意が行われていない場合も「ID3035」が生じる大きな原因となります。
特にSharePoint Online関連の権限(例:Sites.ReadWrite.AllSites.Read.All)は、管理者の同意が必要なケースがほとんどです。アプリを新規登録した際、ポータル上で「Grant admin consent for <テナント名>」を実行しないと、トークン取得時には権限が不十分のままとなり、SharePoint REST APIへのアクセスがブロックされる恐れがあります。

3. APIエンドポイントの混乱(SharePoint RESTとGraph API)

エンドポイントの指定が誤っていることも原因の一つです。

  • SharePoint REST API: https://&lt;テナント名&gt;.sharepoint.com/_api/web/lists
  • Microsoft Graph API: https://graph.microsoft.com/v1.0/sites/&lt;siteId&gt;/lists
    上記のように、SharePoint固有のAPIとGraph APIではURLが異なります。また、取得するトークンのスコープも異なるケースが多いので、使っているAPIに合わせて設定を見直すことが重要です。

4. リクエストヘッダーやHTTPメソッドの不備

SharePoint REST APIへアクセスする際は、AcceptContent-Typeヘッダーを明示的に指定する必要がある場合があります。例えば、JSONを返してほしい場合は下記のように設定することが推奨されます。

Accept: application/json;odata=nometadata
Content-Type: application/json;odata=nometadata

特にNode.jsのfetchaxiosなどのライブラリを使う場合、デフォルト設定だとヘッダーが不足していることがあるため、適切に追加しておくことが大切です。

5. SharePointサイト側の権限設定

Azure側でのアプリ権限だけでなく、SharePointサイト自体のアクセス権限設定も確認してください。SharePoint Online上でリストにアクセスするためには、アプリとサイトの両方で適切な権限が与えられている必要があります。管理者特権を用いてテナント全体へのアクセスを許可している場合でも、一部のサイトやリストで細かく権限管理をしている場合はエラーが出ることがあります。

エラー解決の手順と対処法

ここでは、エラーを解決するための包括的な手順を解説します。トラブルシューティングを行う際、以下の流れで確認していくと効率的です。

ステップ1:アプリ登録の権限と同意の状態を再確認

  1. Azureポータルにログインし、「アプリの登録」サービスを開きます。
  2. 対象となるアプリを選択し、「APIのアクセス許可」セクションを確認。
  3. SharePoint(または必要に応じてMicrosoft Graph)の権限が正しく追加されているかをチェック。
  4. 「管理者による同意が必要」と表示される権限がある場合は、テナント管理者による同意が完了しているかどうかもチェック。
  5. 必要に応じて、「Grant admin consent for <テナント名>」をクリックして同意を付与。

ステップ2:アクセストークン取得のスコープ設定を修正

Node.jsコードでトークンを取得する際のスコープ指定が正しいかを確認します。例えば、SharePoint REST APIにアクセスする場合、下記のようにscopeを指定します。

const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
const body = 
    `client_id=${encodeURIComponent(clientId)}` +
    `&scope=${encodeURIComponent('https://<テナント名>.sharepoint.com/.default')}` +
    `&client_secret=${encodeURIComponent(clientSecret)}` +
    `&grant_type=client_credentials`;

Graph APIを使う場合は「https://graph.microsoft.com/.default」とします。resourceを「https://myurl.sharepoint.com」だけにしていると、v2.0エンドポイントの仕様上エラーにつながる可能性がありますので注意しましょう。

ステップ3:リクエストヘッダーの追加とHTTPメソッドの確認

SharePoint REST APIにデータを取得する際、GETメソッドを使っていること、またAcceptContent-Typeに適切な値を設定しているかをチェックします。たとえば、Node.jsのfetchを用いる場合は以下のようにヘッダーを指定すると良いでしょう。

async function fetchSharePointData() {
    const accessToken = await getToken();
    const apiEndpoint = 'https://<テナント名>.sharepoint.com/_api/web/lists';

    const response = await fetch(apiEndpoint, {
        method: 'GET',
        headers: {
            Authorization: `Bearer ${accessToken}`,
            Accept: 'application/json;odata=nometadata'
        }
    });

    const data = await response.json();
    console.log(data);
    return data;
}

ヘッダーを追加するだけで、エラーが解消する事例もあるため、まずはここを試す価値があります。

ステップ4:SharePointサイト側の権限をチェック

アプリケーションの権限は十分でも、アクセスしようとしているサイトやリストに対してユーザー/アプリがアクセス許可を持っていないケースがあります。特に制限が厳しいサイトコレクションやチームサイトにアクセスする場合、サイト単位の設定によってはブロックされる可能性があります。
テナント管理者やサイトコレクション管理者が、該当サイトへアプリ権限を付与しているか確認し、不足があれば追加入力を行いましょう。

具体的なサンプルコードと解説

以下に示すのは、Node.jsでSharePoint REST APIにアクセスする際の典型的なサンプルです。問題が起きやすいポイントと対処法をコード内のコメントに注釈します。

const fetch = require('node-fetch');

async function getAccessToken() {
    const tenantId = 'xxxxx-xxxx-xxxx-xxxx';
    const clientId = 'yyyyy-yyyy-yyyy-yyyy';
    const clientSecret = 'zzzzz-zzzz-zzzz-zzzz';
    // スコープは必ず/.defaultを付与した形式
    const scope = 'https://<テナント名>.sharepoint.com/.default';

    const tokenUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token`;
    const bodyParams = new URLSearchParams();
    bodyParams.append('client_id', clientId);
    bodyParams.append('client_secret', clientSecret);
    bodyParams.append('scope', scope);
    bodyParams.append('grant_type', 'client_credentials');

    const response = await fetch(tokenUrl, {
        method: 'POST',
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded'
        },
        body: bodyParams
    });

    if (!response.ok) {
        throw new Error('トークン取得に失敗しました。ステータスコード: ' + response.status);
    }

    const data = await response.json();
    return data.access_token;
}

async function fetchSharePointLists() {
    const apiEndpoint = 'https://<テナント名>.sharepoint.com/_api/web/lists';
    try {
        const token = await getAccessToken();
        const response = await fetch(apiEndpoint, {
            method: 'GET',
            headers: {
                Authorization: `Bearer ${token}`,
                Accept: 'application/json;odata=nometadata'
            }
        });

        if (!response.ok) {
            // エラーコードを確認してデバッグに役立てる
            throw new Error('SharePoint リクエストに失敗しました。ステータスコード: ' + response.status);
        }

        const result = await response.json();
        console.log(result);
    } catch (err) {
        console.error('エラーが発生しました:', err);
    }
}

// 実行
fetchSharePointLists();

この例では、特に注意すべきポイントは以下の通りです。

  • トークン取得時のスコープ指定: 「https://&lt;テナント名&gt;.sharepoint.com/.default」を設定。
  • アクセストークンの適用: Bearer &lt;token&gt; 形式でAuthorizationヘッダーに設定。
  • HTTPメソッドとヘッダー: GET を用い、Acceptを適切に設定してSharePointからJSON形式のレスポンスを期待する。

よくある質問(FAQ)

質問回答
Q1: Graph APIでも同じID3035エラーは発生しますか?A1: 原因や状況によっては発生します。特にスコープ設定や同意が正しく行われていないと、Graph APIでも同様のエラーとなるケースがあります。
Q2: アプリの登録で「Sites.Read.All」と「Sites.ReadWrite.All」など複数の権限を付けたら解決しますか?A2: 権限を増やすことは一つの方法ですが、重要なのはテナント管理者が「Grant admin consent」を行っているかです。権限を付与しても同意がなければ利用できません。
Q3: トークンは取得できるが常にエラーになる場合、何を疑うべきですか?A3: まずはアクセストークンが正しいスコープを含んでいるかをチェックしましょう。また、リクエストのヘッダーやサイト側の権限設定も見直すとよいでしょう。

トラブルシューティングのヒント

ログの活用

Node.jsでエラーハンドリングを行う際に、ステータスコードやレスポンスボディの中身も細かくログに出力すると原因追及がスムーズです。response.statusだけではなく、response.headersresponse.text()response.json())で詳細を確認してみると、Microsoft側から追加ヒントが得られる場合があります。

PowerShellや別ツールでの検証

同じテナント上でPowerShell(PnP.PowerShellやMicrosoft Graph PowerShell)などを用いて同様のAPIにアクセスし、エラーが出るかどうかを試すと切り分けが可能です。Node.jsのコードに問題があるのか、あるいはテナントの設定やアプリ登録の不備なのかを比較検証できます。

Graph Explorerでの試験

Graph APIを利用するシナリオならば、Graph Explorerを使ってアクセスを試してみましょう。権限を設定した状態で正常にデータが取得できるか検証することで、権限レベルの問題かどうかを判断できます。

アプリケーションの種類を再考する

Azure ADでアプリを登録する際、「シングルページアプリ (SPA)」「パブリッククライアント」「Webアプリ / API」などアプリケーションの種類が異なると、利用できるフロー(Auth Codeフロー、Client Credentialsフローなど)や認証方式が変わります。Node.jsでバックエンドとして動かす場合は「Webアプリ / API」を選択するケースが多いので、設定がずれていないかもチェックしてください。

まとめと今後のアクション

「ID3035: The request was not valid or is malformed.」エラーは、一見すると単なる不正リクエストのように思えますが、実際にはトークンのスコープ設定不備アプリ登録の権限承認不足リクエストヘッダーの不足など多岐にわたる原因が考えられます。特にSharePoint REST APIを利用する場合は、Graph APIと違うエンドポイントやスコープ指定を正しく行う必要があるため、注意が必要です。

以下のポイントを改めてチェックし、快適なNode.jsとSharePoint連携を実現しましょう。

  1. アプリの登録内容と管理者同意: 必要な権限を追加後、テナント管理者による同意が行われたかを確かめる。
  2. スコープに /.default を付与: SharePointの場合はhttps://&lt;テナント名&gt;.sharepoint.com/.defaultを指定。
  3. リクエストヘッダーを適切に設定: Acceptなどが不足していないか確認する。
  4. 対象サイトやリストの権限: テナント全体の権限はあっても、個別サイトでアクセス制限されていないかチェック。
  5. 他ツールでの再検証: PnP.PowerShellやGraph Explorerを使い、同じエンドポイントで試す。

上記の工程を踏んでもなお解決しない場合、Microsoft Q&A Community (SharePointタグ)や開発者向けフォーラムなどで質問し、認証ログや詳細な設定情報を共有しつつ、さらなるアドバイスを得るとよいでしょう。多くのユーザーが似た課題に直面しており、同様の症状の解決策が既に投稿されている可能性があります。

コメント

コメントする