問題の概要モダンブラウザのセキュリティ設定は厳格です。フロントエンドが「シンプルではない」リクエスト(application/json ボディを含む POST や Authorization ヘッダーを含むリクエストなど)を送信しようとすると、ブラウザは即座にリクエストを送信しません。代わりに、まず OPTIONS リクエストを送信します。これが「プリフライト(preflight)」チェックです。
ブラウザは本質的に「このデータを送信しても大丈夫ですか?」と問い合わせています。サーバーが成功のステータスコード(通常は 200 または 204)以外を返すと、ブラウザは接続を遮断します。実際のデータ送信すら試みられません。これが「does not have HTTP ok status」というエラーが表示される理由です。
根本的な原因- OPTIONS リクエストの処理漏れ: バックエンドのルートが GET または POST のみを許可している場合、OPTIONS リクエストがサーバーに届くと 405 Method Not Allowed が返されます。- 認証ミドルウェアの干渉: ほとんどの API は JWT や API キーを必要としますが、ブラウザはプリフライトリクエストにこれらの認証情報を含めません。認証ロジックが早すぎる段階で実行されると、トークンがないため 401 Unauthorized が返されてしまいます。- グローバルな CORS 設定の欠如: サーバーに、すべてのエンドポイントでプリフライトチェックを処理するための包括的なポリシーが設定されていません。## エラーの修正方法(フレームワーク別)### 1. Node.js (Express)Express では、cors パッケージを使用するのが標準的な解決策です。ただし、記述する場所が非常に重要です。ルート設定よりも前、さらに重要なのは、あらゆる認証ロジックよりも前に初期化する必要があります。
const express = require('express');
const cors = require('cors');
const app = express();
// 1. すべてのルートで即座に CORS を有効化する
app.use(cors());
// 2. 必要に応じて明示的に OPTIONS を処理する
app.options('*', cors());
// 3. 認証は必ず CORS の後に配置する
app.use(myAuthMiddleware);
app.post('/api/data', (req, res) => {
res.json({ message: 'Success' });
});
2. Nginx の設定Nginx レベルでプリフライトを処理する方が、リクエストがアプリケーションコードに到達するのを待つよりも高速な場合があります。以下のブロックを使用して OPTIONS リクエストをインターセプトし、即座に 204 ステータスを返します。
location /api/ {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Authorization,Content-Type,Accept,Origin';
add_header 'Access-Control-Max-Age' 1728000; # 20日間キャッシュ
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_pass http://backend_server;
}
3. Python (Flask)Flask アプリの場合は、flask-cors 拡張機能を使用します。セットアッププロセスの早い段階で app オブジェクトをラップするようにしてください。
from flask import Flask
from flask_cors import CORS
app = Flask(__name__)
# これにより、すべてのルートとメソッドで CORS が有効になります
CORS(app)
@app.route("/api/data", methods=["POST", "OPTIONS"])
def handle_data():
return {"status": "ok"}
4. Spring Boot (Java)Spring Boot では、グローバル設定を定義できます。これにより、エンドポイントが通常は特定の権限を必要とする場合でも、OPTIONS メソッドが許可されるようになります。
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS")
.allowedHeaders("*");
}
}
「隠れた」原因:認証ミドルウェアCORS 対応を追加した後もエラーが発生しますか?その場合、処理の順序に問題がある可能性があります。サーバーが CORS をチェックする前にログイン用のトークンをチェックしていると、プリフライトは毎回失敗します。
標準的なブラウザは、OPTIONS プリフライト中に Authorization ヘッダーを送信しません。サーバーのコードが以下の例のようになっていると、動作しなくなります。
app.use(verifyJWT); // ❌ トークンをチェックして失敗する
app.use(cors()); // ❌ ここまで到達しない
verifyJWT 関数はトークンのない OPTIONS リクエストを受け取り、401 Unauthorized を返します。その結果、ブラウザでエラーが発生します。これを修正するには、必ず CORS ミドルウェアをスタックの最上部に移動してください。
検証修正結果のテストをブラウザだけに頼らないでください。ターミナルから curl を使用してプリフライトリクエストをシミュレートします。これにより、バックエンドの問題とフロントエンドのコードのバグを切り分けることができます。
curl -v -X OPTIONS http://localhost:3000/api/data \
-H "Access-Control-Request-Method: POST" \
-H "Origin: http://localhost:8080"
出力結果で以下の 2 点を確認してください:
- HTTP ステータスコードが
200 OKまたは204 No Contentであること。- ヘッダーにAccess-Control-Allow-Originが含まれていること。## 予防策- 優先順位: アプリケーションロジックにおいて、CORS を最初のミドルウェアにします。- CI/CD テスト:OPTIONSメソッドを使用して API を明示的に呼び出すテストケースをパイプラインに追加します。- ログ監視: サーバーログで 401 または 405 ステータスコードを確認します。これらがOPTIONSリクエストと同時に発生している場合、ミドルウェアの順序が間違っています。

