Nuxt4 + Amplify(Cognito)で起きた「Missing bearer token」問題と SSR 認証のリアル

この記事では、Nuxt4 と AWS Amplify(Cognito 認証)を組み合わせた掲示アプリ開発の中で起きた、 「ユーザー一覧ページが初期表示で Missing bearer token エラーになる」 という問題と、その修正内容・背景技術について紹介します。

同じ構成でアプリを作っている方や、Nuxt + Cognito の SSR まわりでハマる方の参考になれば幸いです。


🔧 プルリクの概要

問題が発生したのは 管理者向け「ユーザー一覧ページ」です。

ページの初期表示時に、内部で /api/users を叩いてユーザー一覧を取得します。しかし SSR(サーバーサイドレンダリング)により、以下のコードがサーバー側で実行されました。

await useAsyncData('admin-users-list', () => $fetch('/api/users'))

ところが Cognito 認証が必要な /api/users では Authorization ヘッダーに Bearer Token が必要

しかし SSR のタイミングでは Cognito のトークンがまだ存在せず、結果として:

Missing bearer token

というエラーが発生。

このプルリクは、初期表示時に Missing bearer token が出る問題を修正するプルリクです。


🎯 修正内容:SSR をやめて "クライアント側で" 認証後に fetch する方式に変更

最終的には、API の呼び出しタイミングを SSR → クライアント側へ移動することで解決しました。

修正後の流れは次のとおり:

onMounted(async () => {
  start()      // 認証情報の準備
  await sync() // LocalStorage の Cognito トークンを同期
  await loadUsers() // 認証ヘッダー付きで /api/users を fetch
})

要点は以下。

  • SSR(サーバー側)では Cognito トークンを取得できない
  • sync() によりクライアント側でトークンが揃う
  • buildHeaders() が正しい Authorization ヘッダを生成
  • $fetch('/api/users') が正常に実行されるようになる

結果、Missing bearer token は完全に解消されました。


🔍 なぜ SSR で Missing bearer token が出たのか?

✔ Cognito は LocalStorage 管理の SPA 認証方式

Amplify(Cognito)の認証情報はこう管理されています:

  • accessToken → LocalStorage
  • idToken → LocalStorage
  • refreshToken → LocalStorage

👉 SSR(サーバー側)では LocalStorage が存在しないため、トークンを取得できない


SSR の useAsyncData() は “サーバー側で” 一度実行される

そのため…

  • まだ認証同期が済んでいない
  • LocalStorage にアクセスできない
  • 認証ヘッダーに必要な Bearer Token を組み立てられない

という状況になります。

つまり構造的に、

Cognito の認証トークンは SSR では読み取れない

という問題が根本原因です。


🤔 そもそも、Cognito で SSR 認証を行わないのは一般的なのか?

結論:はい、一般的です。むしろ Cognito は SSR 認証が苦手なサービスです。

理由は明確で、

  • Cognito 認証は SPA / モバイル向け
  • 認証情報はブラウザに保存される(LocalStorage)
  • SSR(サーバー)からセッションを参照できない
  • HttpOnly Cookie による SSR 認証方式を前提としていない

この構造は、SPA 向けには最高に便利ですが、 SSR アプリケーションには向かない側面があります。


📈 一方で、SSR 認証が必要になるユースケースもある

たとえば:

① URLを直接開いたときも SSR で認証を反映したい管理画面

初期描画からユーザー一覧やダッシュボードを表示したい場合。

SEO が必要なページ+ログイン状態による表示差分

ブログの下書きプレビューなど。

SSR でサーバー側 API にユーザー認証情報を渡したいケース

SSRAPI → DB」のようにサーバー中心で処理する場合。

こういった “SSR の時点でログイン状態を使いたい” ユースケースでは、 Cognito の LocalStorage トークン方式では限界があるのです。


🔥 Auth0 を使うとどうなる? → SSR 認証が実現できる

Auth0 には HttpOnly Cookie 方式の SSR 認証が組めます。

具体的には:

  1. Universal Login でログイン
  2. サーバー側(Nuxt)でコード交換
  3. access_token を HttpOnly Cookie に保存
  4. SSR 時に Cookie を読み取り、認証情報を復元
  5. SSR の useAsyncData() でも認証済み fetch が可能

つまり、今回の PR のような「SSR で Missing bearer token」が Auth0 なら根本的に発生しない構造が作れます。


📝 まとめ:Nuxt × Cognito の SSR は構造的に難しい

今回発生した「Missing bearer token エラー」は、 Cognito が SSR 認証に向かないことによる構造的な問題でした。


✅ 課題

SSRuseAsyncData() がサーバー側で実行される ↓ サーバー側では Cognito トークンがない ↓ Authorization ヘッダを生成できない ↓ Missing bearer token が発生


✅ 解決

クライアント側 onMounted() で認証同期後に fetch → 認証ヘッダーも正しく付与され正常に動作


✅ 学び

  • Cognito の LocalStorage トークン方式は SSR と相性が悪い
  • SSR 認証が必要なアプリなら、Cookie セッション型(Auth0など)が向く
  • Nuxt / Amplify 構成では「認証 APICSR で呼ぶ」が王道

🎉 おわりに

今回の修正は一見小さな変更ですが、背景には Cognito の認証方式と SSR の本質的な相性問題 という深い技術的テーマがありました。

Nuxt + Amplify で SSR を使う方や、Cognito の制約に悩む方の参考になれば幸いです。

以下は、URL を自然に含めた「ブログの最後に添えるパッチ文章」です。内容を壊さず、一般読者にも読みやすい形でまとめています。


今回の調査の中で、AWS Amplify(Authentication モジュール)が HttpOnly Cookie をサポートし始めているという公式アナウンスも確認しました。

特に、2025年3月に公開された以下の発表では、 Next.js などのサーバーサイドレンダリングSSR)アプリ向けに、HttpOnly Cookie を用いたセキュアな認証管理がサポートされたと明記されています。

🔗 参考: https://aws.amazon.com/jp/about-aws/whats-new/2025/03/aws-amplify-httponly-cookies-server-rendered-next-js-applications/

Cognito 自体が直接「HttpOnly Cookie に ID トークン/Access トークンを保存する」仕組みを提供しているわけではありませんが、 Amplify 側で Cookie ベースのセッション管理を実現するレイヤーを提供し始めたという点は非常に興味深い動きです。

この部分はまだ十分に検証できていないため、 Amplify がどのように Cookie を生成し、署名/暗号化し、検証するのか Nuxt / Nitro で同様の構成が再現できるのか といったポイントを引き続き調査し、別記事として整理する予定です。