Develop環境でCORSエラー(Vercel認証起因)

CORSエラーには何度も悩まされていますが、過去最長20時間以上ハマりました。
限られた条件のもとで発生するエラーなので、原因を特定するのに苦労しました。
結論から
対象になるのは、下記の条件の時です。
- vercelを使用してデプロイしている
- localhostでは問題なく動作する
- メインブランチではないブランチで確認している時に起きている
- 認証が必要なAPIを使用している
- 401エラー
一つでも当てはまらない場合は、別の理由か、複数の原因によるものかもしれません。
全て当てはまる場合は、Vercel認証を疑ってみてください。
vercelのSetting > Deployment Production > Vercel Authentication が有効になっている場合は、無効にします。
するとアッサリとプリフライトリクエストが通るようになりました。
ことの背景
今回は、フロントエンドはNext.js、バックエンドはFastAPIで作成していました。
APIに認証が必要なエンドポイントがあり、headerにTokenを付与してリクエストを送っていました。
よくあるCORSエラーの原因として、バックエンドからのレスポンスヘッダーにAccess-Control-Allow-Originが設定されていないことが挙げられます。
また、クレデンシャル情報を含むリクエストを送る場合は、場合によってさらに設定が必要だったりと、いくつかの条件が重なることがあります。
今回はそれらは問題なく、バックエンドのミドルウェアにてDevelop環境のパスも許可しており、localhost環境でも動作していました。
そしてdevelopブランチをデプロイし確認すると、CORSエラーが発生しました。
FastAPI側でoptionsのデコレータを追加して204を返すようにしてみました。@app.options("/hoge/",status_code=204)
それでも401が返ってきます。
ローカルでは問題ないことから、vercelの設定が原因であると考え
FastAPIをvercelにデプロイするときの設定(vercel.json)にheader情報を追加してみましたが、それでもしつこく401エラーです。
認証が必要なAPIをコメントアウトしても401が返ってきました。
この時点で意味がわからなくなりました。
認証の処理を通ってないのに401が返ってくるなんて、デプロイ自体がおかしいのかと思いました。…が、デプロイは成功しています。
そもそもvercel.jsonで設定したheaderやFastAPIのミドルウェアで設定したheaderが反映されていません。
プリフライトリクエストのレスポンスヘッダーを確認すると、Access-Control-Request-Headersはauthorization, content-type, のみ、Access-Control-Request-MethodはGETのみ…。明らかに自分の設定したheaderが反映されていないことがわかりました。
原因 Vercel認証
この401の正体は、自分で設定していた認証ではなく、Vercelの認証でした。
アクセス権を持つVercelユーザーのみにデプロイメントへのアクセスを制限するための機能です。(すべてのプランが対象)
自分でオンにした記憶がないので、デフォルトでオンになっているのか…!?と調べると、2023/11のvercelの記事を発見。
デプロイメント保護は、すべての新規プロジェクト に対してデフォルトで有効になり 、保護オプションの完全なセットが一般に利用可能になりました。
https://vercel.com/changelog/deployment-protection-is-now-enabled-by-default-for-new-projects
今回新しく作ったプロジェクトだったので、おそらくこれだと思います。
(デフォルトでオンになっているということは、本番公開した時はオンに戻した方が良いかもしれない…。)
……しかし、まずプレビューで確認してから本番公開することを考えると、プレビューだけにこんな設定があってしかもデフォルトでオンになっているのは…結構罠なのではと思いました。
発見が遅れた理由
まず認証が必要ない時には問題なく動作していたこと。
認証を入れた時にAuthorizationヘッダーをつけたことによってプリフライトリクエストが飛び、その時にCORSエラーとなりました。
こんな状況だったので、そのAuthorizationヘッダーの認証が原因だと思い込んでしまいました。(ローカルでいけてることをもう少し加味すべきだった)
さらに、pythonをvercelにデプロイする際にはvercel.jsonで設定を行いますが、その設定方法の情報が少ないため、このあたりが原因かもとわからないなりに設定し、不毛なコミット&デプロイが積み重なっていきました…。(数えたら105回やってた)
結局はvercel.jsonはあまり特別なことをせずともこの形でいけたので、載せておきます。(api/main.py
がFastAPIのエントリーポイントです。)
{
"builds": [
{
"src": "api/main.py",
"use": "@vercel/python"
}
],
"rewrites": [
{
"source": "/(.*)",
"destination": "api/main.py"
}
]
}
まとめ
20時間越えの戦いとなりましたが、CORSについて勉強せよとの神のお告げだったのかもしれません。
そもそもCORSについての知識が甘かったからコードに自信が持てなかったことも大きいです。諦めなくてよかったです。
稀なケースかとは思いますが、誰かの助けになれば幸いです。