Skip to main content
技術

microCMS の API 権限ミスで下書き記事が本番公開されていた話

microCMS の API キー権限の設定ミスにより、下書き記事が本番サイトに表示されていた問題の原因特定と修正を解説。publishedAt[exists] フィルターと API 権限設定の二重防衛で解決した体験談です。

microCMS の API 権限ミスで下書き記事が本番公開されていた話

はじめに

ブログを公開してしばらくして、あることに気づきました。microCMS の管理画面で「下書き」にしていた記事が、本番サイトに表示されている——。

「下書きのはずなのに、なぜ?」と思いながら調査した結果、原因は API キーの権限設定と、コード側のフィルター漏れの組み合わせでした。設定画面でチェックを入れていた項目が、思わぬ形で本番に影響していたのです。

この記事では、なぜこの問題が起きたのか、どう修正したのかを整理します。同じ構成でブログを運営している方の参考になれば孉いです。


なぜ起きたのか—— 3 つの原因が重なっていた

原因 1:API キーに「下書きコンテンツの全取得」権限があった

microCMS の API キーには、細かな権限設定があります。そのうちのひとつが「下書きコンテンツの全取得」という権限です。

通常、API でコンテンツを取得すると公開済みの記事だけが返ってきます。しかしこの権限をオンにすると、draftKey(下書きプレビュー用のキー)を使わなくても、下書き状態の記事もまとめて取得されるようになります。

私はこの権限を、AI ツール(Claude Code・Codex)から下書き記事を確認できるようにするためにオンにしていました。意図した設定でしたが、それが思わぬ副作用を生んでいました。

原因 2:本番サイトのビルドに同じ API キーを使っていた

Astro は静的サイトジェネレーターなので、ビルド時に microCMS から記事データを取得して HTML を生成します。このビルド時の API 呈び出しにも、AI ツール用と同じ API キーを使っていました。

microCMS の hobby プランでは API キーを 1 つしか作成できません。そのため「AI ツール用」と「本番サイト用」でキーを分けることができず、同じキーを共用する運用になっていました。

結果として、「下書きコンテンツの全取得」権限を持つキーでビルドが走り、下書き記事のデータも取得されることになっていました。

原因 3:公開済み記事だけを絞り込むフィルターがなかった

getArticles() 関数は、取得した記事をそのまま返していました。「公開済みかどうか」を絞り込む処理がなかったため、API から返ってきた下書き記事もそのままビルドに使われていました。

// 修正前:フィルターなし
const response = await client.get({
  endpoint: 'articles',
  queries: { orders: '-publishedAt', limit: 100 }
});

この 3 つが重なって、「下書き記事が本番サイトに表示される」という状態が生まれていました。


調査・特定の流れ

「下書きが見えている」と気づいてから、まず疑ったのは microCMS 側の設定でした。

管理画面を確認すると、記事のステータスは確かに「下書き」になっています。では、なぜ本番に出ているのか。

次に疑ったのが API キーの権限です。管理画面の API キー設定を開いて権限一覧を確認したところ、「下書きコンテンツの全取得」にチェックが入っていました。

「この権限があると、ビルド時にも下書きが取得されるのでは?」と仮説を立て、src/lib/microcms.ts のコードを確認しました。すると、getArticles() 関数に published な記事に絞り込む処理がないことがわかりました。

原因の特定は比較的スムーズでしたが、「設定画面のチェックがコードの動作に影響する」という気づきは、改めて API キー権限の重さを認識させてくれました。


修正方法——コードと権限の 2 段構えで対応する

アプローチ 1:コードレベルで publishedAt[exists] フィルターを追加する

microCMS では、publishedAt フィールドが存在する記事が「公開済み」の記事です。下書き記事には publishedAt が存在しません。

この性質を利用して、publishedAt[exists] というフィルターを追加することで、公開済み記事だけを取得できます。

// 修正後:publishedAt が存在する記事のみ取得
export async function getArticles(queries?: MicroCMSQueries) {
  const publishedFilter = 'publishedAt[exists]';
  const mergedFilters = queries?.filters
    ? `${publishedFilter}[and]${queries.filters}`
    : publishedFilter;

  const response = await getFromMicroCMS<Article>('articles', {
    orders: '-publishedAt',
    limit: 100,
    depth: 2,
    ...queries,
    filters: mergedFilters,
  });
  // ...
}

ポイントは、既存の queries?.filters と AND 結合していることです。カテゴリ絞り込みなど他のフィルターが渡されても、公開済み条件と両立できるようにしています。

アプローチ 2:API 権限レベルで「下書きコンテンツの全取得」を外す

microCMS の管理画面から、API キーの「下書きコンテンツの全取得」権限のチェックを外します。

この設定を外すと、API キーを使ったリクエストでは公開済みコンテンツしか返ってこなくなります。コード側のフィルターが不要になるほど根本的な対応です。

ただし注意点があります。この権限を外すと、AI ツール(Claude Code・Codex)からも下書き記事が確認できなくなります。下書き状態の記事を AI ツールで確認したい場合は、アプローチ 1 のコードレベル対応と組み合わせて判断してください。

結論:両方やるのが最も安全

私の場合は以下の判断をしました。

  • API 権限:「下書きコンテンツの全取得」を外す(本番サイトへの漏れを API レベルで防ぐ)
  • コードpublishedAt[exists] フィルターを残す(万が一権限設定が変わっても守られる)

どちらか片方でも問題は解決しますが、 2 つを組み合わせることで「コードが変わっても安全、権限設定が変わっても安全」という状態になります。


hobby プランで API キーが 1 つしか使えない問題

理想的には「AI ツール用(下書き権限あり)」と「本番サイト用(公開のみ)」でキーを分けるべきです。そうすれば AI ツールからは下書きが見えつつ、本番サイトには公開済み記事しか取得されない構成が作れます。

しかし microCMS の hobby プランでは API キーを 1 つしか作成できません。今回のような設定が複雑になる背景には、このプランの制約がありました。コードレベルのフィルターで対応するのが、hobby プランでの現実的な解決策です。


学んだこと——設定ミスは気づきにくい

今回の問題で改めて感じたのは、設定画面のチェックボックス 1 つが、コードの動作に大きく影響するということです。

API キーの権限設定は「管理画面の設定」という感覚で軽く見ていましたが、それが本番サイトのコンテンツ取得に直結していました。こういった設定ミスは、意図した通りに動いているときは気づかず、予期しない副作用として現れます。

また、「同じキーを複数の用途で使い回す」構成は、権限の影響範囲が広くなりやすく、問題が起きたときに原因の特定が難しくなります。キーの用途と権限の対応関係を整理して管理することの大切さを実感しました。


まとめ

今回の問題と対応をまとめると次の通りです。

原因

対応

API キーに「下書きコンテンツの全取得」権限があった

権限のチェックを外す

本番サイトのビルドに同じキーを使っていた

hobby プランの制約上、キーの共用は継続

コード側に公開済み記事の絞り込みがなかった

publishedAt[exists] フィルターを追加

設定と実装の両方で対処することで、二重の安全策になります。microCMS を使っているブログオーナーは、API キーの権限設定と getArticles() の実装を一度確認してみることをおすすめします。