1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【CORS】Access to fetch at 'http://localhost:3000/api/' from origin 'http://localhost:5173' has been blocked by CORS policyエラーが発生する

1
Posted at

はじめに

Express.jsを使って簡易APIを作成、アクセスしようとすると、表題のエラーが発生しました。
解決方法とCORSについて学んだことをまとめます。

問題

異なるポートでサーバーを起動、ブラウザ上で実行すると下記のエラーが発生

エラー内容

Access to fetch at 'http://localhost:3000/api/' from origin 'http://localhost:5173' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.

ポートを変えて以下のサーバーを起動していました。

原因

ブラウザが同一オリジンポリシーを適用していたため。

同一オリジンポリシーとは?

ブラウザに搭載されたセキュリティ上の制約

MDNより引用

同一オリジンポリシーは重要なセキュリティの仕組みであり、あるオリジンによって読み込まれた文書やスクリプトが、他のオリジンにあるリソースにアクセスできる方法を制限するものです。

同一オリジンとは

リクエスト元、リクエスト先のドメイン(スキーム、ホスト、ポート番号)が同じであること

同一オリジンの例
http://localhost:5173/apihttp://localhost:5173/users

また、自身とは異なるドメインに対してリクエスト送信することをクロスオリジン通信と呼びます。

クロスオリジンの例

クロスオリジン通信の場合、ブラウザがこのリクエストは危険と判断しエラーを返します。
そのため、表題のエラーが発生していました。

解決方法

CORSの仕組みを使い、http://localhost:3000 で定義したサーバー側に応答ヘッダを定義する

CORSとは

CORS(Cross-Origin Rescource Sharing)
同一オリジンポリシーによる制限を解除するために考えられた仕組み

MDNより引用

オリジン間リソース共有 (Cross-Origin Resource Sharing, CORS) は、 HTTP ヘッダーベースの仕組みを使用して、あるオリジンで動作しているウェブアプリケーションに、異なるオリジンにある選択されたリソースへのアクセス権を与えるようブラウザーに指示するための仕組みです。ウェブアプリケーションは、自分とは異なるオリジン (ドメイン、プロトコル、ポート番号) にあるリソースをリクエストするとき、オリジン間 HTTP リクエストを実行します。

オリジン間リクエストとは、例えば https://domain-a.com で提供されているウェブアプリケーションのフロントエンド JavaScript コードが fetch() を使用して https://domain-b.com/data.json へリクエストを行うようなものです。

CORSの動作フロー

以下の流れでクロスオリジンリクエストの許可のやりとりを行います。

  1. ブラウザ:「このAPIにアクセスしていいか?」(OPTIONSリクエスト)
  2. サーバー:「このオリジンからならOK」(Access-Control-Allow-Origin)
  3. ブラウザ:「本番のリクエスト送ります」(実際のGET/POST等)

1.OPTIONSリクエスト(プリフライトリクエスト)

HTTPヘッダをつかってOPTIONSメソッドによるリクエストを行います(条件によって省略可)

HTTPヘッダには以下を定義する

ヘッダ 中身
Origin リクエスト要求者のオリジン
Access-Control-Request-Method 送信を許可してほしいHTTPメソッド
Access-Control-Request-Headers 送信を許可してほしいHTTPヘッダ

2.リクエストの判断を行い、レスポンスの返却

サーバーはリクエストを受けると以下の情報をレスポンスとして返却します

ヘッダ 中身
Access-Control-Allow-Origin 送信を許可するオリジン
Access-Control-Allow-Methods 送信を許可するHTTPメソッド
Access-Control-Allow-Headers 送信を許可するHTTPヘッダ
Access-Control-Allow-Credentials クッキーなど送信して良いか
Access-Control-Expose-Headers JavaScriptからの参照を許可する
Access-Control-Max-Age プリフライトリクエストの結果を保存していい期間

3.APIのリクエスト

レスポンスで送信許可を確認したブラウザは、APIのリクエストを送信します。

実処理でCORSを取り入れる

上記フロー「 2.リクエストの判断を行い、レスポンスの返却」の実装を追加することにより解決できました。

import express from "express";
import { generatePortfolioFile } from "./scripts/savePortfolio.ts";

const app = express();
const port = 3000;

+ app.use((req, res, next) => {
+   res.header("Access-Control-Allow-Origin", "http://localhost:5173"); // http://localhost:5173のオリジンを許可
+   res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS"); // 送信を許可するHTTPメソッド
+   res.header(
+     "Access-Control-Allow-Headers",
+     "Origin, X-Requested-With, Content-Type, Accept, Authorization"
+   ); // 送信を許可するHTTPヘッダ
+ 
+   if (req.method === "OPTIONS") {
+     res.sendStatus(200);
+   } else {
+     next();
+   }
+ });

app.post("/api/generate-portfolio", async (req, res) => {
  const { qiitaId, githubId } = req.body;

  try {
    await generatePortfolioFile(qiitaId, githubId);
    res.status(200).send("Portfolio generated successfully");
  } catch (error) {
    res.status(500).send("Failed to generate portfolio");
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

corsパッケージを使うとより簡単に定義できます。

import express from "express";
import { generatePortfolioFile } from "./scripts/savePortfolio.ts";
import cors from "cors";

const app = express();
const port = 3000;

app.use(
  cors({
    origin: "http://localhost:5173", // http://localhost:5173のオリジンを許可
  })
);

app.post("/api/generate-portfolio", async (req, res) => {
  const { qiitaId, githubId } = req.body;

  try {
    await generatePortfolioFile(qiitaId, githubId);
    res.status(200).send("Portfolio generated successfully");
  } catch (error) {
    res.status(500).send("Failed to generate portfolio");
  }
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

おわりに

エラーを通してCORSの仕組みを学ぶことができました。
技術書を読んだだけでは理解しづらい概念もエラーを通して学ぶと理解度が上がると感じた出来事でした。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?