🌟

CORSってなんだ?

2023/11/03に公開

はじめに

少し前に業務でCORSの対応をしたことがあったが、なんとなくの理解度で実装を行なっていたので、知識の整理もかねて調べいったことをメモしていく。

CORSとはなに?

読み方:シーオーアールエス
日本語訳では、"オリジン間リソース共有"
これだけではなんのことさっぱりわからないですね。
リソース共有とあるので、URLの共有や同じって意味ですかね..?
オリジンがなんのことを指しているのか?

オリジンとは

ドメインにプロトコルとポート番号を含んでいるもののことをいいます。
オリジンの例:https//test.co:443
ドメインの例:test.com

※プロトコル、ドメイン、ポート番号どれか1つでも異なる場合は異なるオリジンとして判断される。

CORSとは?

オリジンが何かわかってきたので、少しわかってきたような気がしますが
CORSとは、あるオリジンとあるオリジンのオリジン間アクセスを許可できる仕組みのことを言うことらしい。

この図でいうと④の箇所を許可することのことをいう。

CORSの種類(とても大切なところ)

CORSは2種類の種類があるようです。

Simple Request
単純リクエストと言われ、以下の条件のリクエストに該当する場合は単純リクエストに当てはまる。

  • メソッドはいずれかであること

    • GET
    • POST
    • HEAD
  • リクエストヘッダは以下のいずれかであること(ユーザエージェントによって自動的に付与されるヘッダーを除く)

    • Accept
    • Accept-Langage
    • Content-Langage
    • Content-Type
  • Content-Typeヘッダは以下のいずれかであること

    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain

Preflight Request
プリフライトリクエストと言われるこちらのリクエストは、上記単純リクエスト以外のリクエストが当てはまる。
特徴として、リクエストを送る前にOPTIONSメソッドで対象の異なるオリジンにリクエストを送り
実際のリクエストを送っても問題ないか確認することをしている。

~簡単な送信までの流れ~

  1. クライアントがプリフライトリクエストに該当するHTTPリクエストを送信
  2. ブラウザが、プリフライトリクエストであることを検知して、プリフライトリクエストが送られる。(プリフライトリクエストは、OPTIONSメソッドで送信されリクエストの内容はこの時送信されない。)
  3. プリフライトリクエストを受信したサーバは、Access-Control-Allow-Method、Access-Control-Allow-Origin等の許可するHTTPメソッド・オリジンの情報を返却する。
  4. プリフライトリクエストで許可が得られたため、本体のリクエストが送信される

CORSの必要性

CORSがどういったものなのかわかってきましたが、なぜCORSが必要になるのでしょうか..?
CORSがなければ実装が楽なのに..

Web セキュリティの重要なポリシーの一つに Same-Origin Policy (同一オリジンポリシー)があり、これを守る為に必要となるようです。
もし守らない場合は、XSS(Cross Site Scripting)、CSRF(Cross-Site Request Forgeries)の攻撃を受けてしまう可能性があります。

例えば、ユーザーが Web サイトにアクセスすることで不正なスクリプトが Client (Web ブラウザ) で実行されてしまうことや、Web アプリケーションのユーザーが、意図しない処理を Web アプリケーション (Web Server) 上で実行されることなどが発生していまいます。

具体的な実装

では単純リクエストとプリフライトリクエストを疑似的に起こして対応方法について試してみたいと思います。
フロントのコードがこちらで、バックエンドのコードはこちらです。

単純リクエスト

import express from "express"

const app = express();

app.use((req, res, next) => {
  console.log("リクエストの受信")
  res.header("Access-Control-Allow-Origin","http://localhost:8080")
  next();
})

app.get("/simple-request", function (req, res, next) {
res.send({message:"Hello world Japan!"});
});

app.post("/preflight-request", function (req, res, next) {
  res.send({message:"Hello world Japan!"});
});

app.options('*', function (req, res) {
  res.sendStatus(200);
});


app.listen(5000, () => {
  console.log("Application available!");
});
<template>
  <h1>Checking the operation of cors</h1>
  <button @click="corsSimpleRequest()">Simple Request button</button>
  <button @click="corsPreflightRequest()">Preflight Request button</button>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  name: "App",
  setup: () => {
    const corsSimpleRequest = async () => {
      try {
        console.log("SimpleRequest start");
        const url = "http://localhost:5000/simple-request";
        const res = await fetch(url);
        console.log("SimpleRequest result:", res);
      } catch (e) {
        console.log("エラー発生:", e);
      }
    };
    const corsPreflightRequest = async () => {
      try {
        console.log("PreflightRequest start");
        const url = "http://localhost:5000/preflight-request";
        const option = {
          method: "POST",
          headers: {
            "Content-Type": "application/json",
          },
          body: JSON.stringify({
            message: "PreflightRequest",
          }),
        };
        const res = await fetch(url, option);
        console.log("PreflightRequest result:", res);
      } catch (e) {
        console.log("エラー発生:", e);
      }
    };
    return { corsSimpleRequest, corsPreflightRequest };
  },
});
</script>

まずはbackend.tsのheaderに設定しているコードをコメントアウト。

// res.header("Access-Control-Allow-Origin","http://localhost:8080")

フロントの"Simple Request botton"をクリック。
CORS(単純リクエスト)でエラーが発生していることを確認できます。

単純リクエストの場合は、originのアクセス許可をレスポンスヘッダーに設定すればいいみたいなので、バックエンドで先程コメントした箇所を解除して再度リクエストを行ってみると..

エラーが解消されていることを確認できました!!!

プリフライトリクエスト

import express from "express";

const app = express();

app.use((req, res, next) => {
  console.log("リクエストの受信");
  res.header("Access-Control-Allow-Origin","http://localhost:8080")
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
  res.header('Access-Control-Allow-Headers','Content-Type, Authorization, access_token')
  next();
});

app.get("/simple-request", function (req, res, next) {
  res.send({ message: "Hello world Japan!" });
});

app.post("/preflight-request", function (req, res, next) {
  res.send({ message: "Hello world Japan!" });
});

app.options("*", function (req, res) {
  res.sendStatus(200);
});

app.listen(3000, () => {
  console.log("Application available!");
});

frontend.vueは同じ
次にプリフライトリクエストですが、単純リクエストと同様に
まずはバックエンドのheaderに設定しているコードをコメントアウト。

// res.header("Access-Control-Allow-Origin","http://localhost:8080")
// res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE')
// res.header('Access-Control-Allow-Headers','Content-Type, Authorization, access_token')

フロントの"Preflight Request"をクリック。
CORS(プリフライトリクエスト)でエラーが発生していることを確認できます。

プリフライトリクエストの場合は、originのだけでなくmethodやheaderの許可が必要で
POTIONメソッドのリクエストを対応できるようにする必要があります。

単純リクエストと同様にコメントアウトを解除して再度リクエストを行ってみると..

エラーが解消されていることを確認できました!!!

プログラム(頭にcorsがついているフォルダ)

https://github.com/suguru-3u/web-base/tree/main

まとめ

調べて見るまでは、CORSについて曖昧な理解でしたが、今は少し理解できたと感じています。
新規リソースの作成時は気をつけたいと思います!

参考サイト

https://qiita.com/att55/items/2154a8aad8bf1409db2b
https://qiita.com/eshow/items/e47e7214dc12f130f456

https://qiita.com/chenglin/items/5e563e50d1c32dadf4c3
https://qiita.com/ymbn/items/cc69dc3eccf5a13bbeed

https://zenn.dev/luvmini511/articles/d8b2322e95ff40

・プログラムの対応方法
https://s8a.jp/node-js-express-http-options#実際のコード

Discussion