はじめに
実務でSPA構成で実装しているとCORSエラーに遭遇することがあり、そもそもCORSとはどういうものなのかを上手く説明できないため、学習し直しました。
CORSとは
CORSは Cross Origin Resource Sharing の略です。英語を訳すと、オリジン間でリソースを共有することが可能という意味ですが、それだけでは?だと思いますので、一つずつ解説します。
オリジンとは、簡単に言うと URL のことを指しております。URLとは以下の構成要素からなりなっております。
- スキーム(https、httpなど)
- ホスト(例えばwww)
- ドメイン(例えばhogehoge.co.jp)
- ポート番号(3000や5000など)
リソースとは、 データのことを指します。
CORSは異なるURL間でデータをやりとりすることができる仕組みのことです。
なぜCORSが必要になったのか
そもそもWEB上では異なるURL間でデータをやりとりすることはできません。以下に例を示していきます。
以下は2つのURLはドメインが異なるため、データをやりとりできません。
オリジンA: https://www.hogehoge:3000
オリジンB: https://www.fugafuga:3000
以下は2つのURLはポート番号が異なるため、データをやりとりできません。
オリジンA: https://www.fugafuga:3000
オリジンB: https://www.fugafuga:5000
以下は後者にサブドメイン(api)が付いているため、こちらもダメです。
オリジンA: https://www.fugafuga:3000
以下はもちろん同じURLのため、データをやりとり可能です。
オリジンA: https://www.fugafuga:3000
オリジンB: https://www.fugafuga:3000
以下の場合もデータをやりとり可能です。
オリジンA: https://www.fugafuga:3000
では、なぜWEB上ではオリジンが異なる場合、データをやりとりできないのでしょうか?
もし気軽に異なるオリジン間でデータをやりとりできてしまうと、セキュリティトラブルが発生してしまうからです。
例えば、悪意のある人が攻撃対象者のネット銀行にあるお金を奪いたいと考えていた場合、攻撃対象者のPCにメールで攻撃者が用意したURLを送ります。対象者はURLをクリックすると、自身のネット銀行(https://www.bank.com)から攻撃者が用意したサイト(https://www.evilwebsite.com)にお金を送信することもできてしまいます。
ですが、異なるオリジン間でデータをやりとりできないようにする「同一生成元ポリシー」によって、上記のセキュリティ事故を防ぐことができます。
ただ、「同一生成元ポリシー」があることによって、困ったことがあります。それは、フロント側とサーバ側でオリジンが異なるSPAの開発の場合です。同一生成元ポリシーがあることによって、フロントとサーバ間でデータがやりとりできません。そのような問題を解決するために生まれた仕組みが CORSです。
CORSに関する詳細
CORSを具体的に実装するためには、サーバ側でフロント側のオリジンを許可することによって、データのやり取りが可能になります。サーバ側はExpress、フロント側はHTML、Javascriptで実装してみました。
サーバ側のコードは以下となります。
const express = require("express")
const app = express();
const cors = require("cors")
const PORT = 3000;
app.use(
cors({
origin: "http://127.0.0.1:5500",
methods: ["GET", "POST"],
})
);
app.get("/data", (req, res) => {
res.json({name: "hoge", age: 20});
});
app.listen(PORT, ()=> console.log("server is running"))
ポイントなる箇所が以下です。以下でフロント側のオリジンである「http://127.0.0.1:5500」からのリクエストを許可しております。
サーバ側で許可するというと難しいそうで、複数行に渡る処理なのかと思いましたが、実際に書いてみると簡単に許可できます。
また、methods: ["GET", "POST"]でメソッドGETとPOSTメソッドだけを許可しております。
app.use(
cors({
origin: "http://127.0.0.1:5500",
methods: ["GET", "POST"],
})
);
ご参考までにフロントエンド側は以下のようにコードです。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script defer src="./script.js"></script>
</head>
<body>
<h1>CORS</h1>
</body>
</html>
const fetchdata =() => {
fetch("http://localhost:3000/data", {method: "PUT"})
.then((res) => res.json())
.then((data) => console.log(data));
}
fetchdata();
Githubに今回の学習で作成したコード全量がございます。
RailsでCORSを実現するには以下の記事がわかりやすかったです。
感想
今回はCORSを改めて学んでみて、用語1つ1つの意味を噛み砕いて理解することが大切だと改めて感じました。オリジンはスキーム、ホスト、ドメイン、ポート番号で成り立っているということがちゃんと理解できていなかったため、今回記事を書くことで理解を深めることができました。
また、セキュリティの用語学習だけでは概念だけわかったけど実際にコードに落とし込もうとするとどう書くのか?ということになってしまっていたため、手を動かして学習することが大切だと思いました。
参考資料