👁

GoでCORS対応をしよう

この章では、GoのAPIサーバに対してブラウザからのCORSエラーを体験しつつ、Ginのcorsミドルウェアで解消する方法をハンズオン形式で学習しながら実装します。これにより、CORSの仕組みを理解した上で、ブラウザからのクロスオリジン呼び出しを許可するAPIサーバが構築できるようになります。

1. 本章の概要

1.1 本章の目的

前章 GoでREST APIを作ろう までで、Goで動くREST APIをcurlから呼び出せるところまで確認しました。実際のアプリケーションでは、ブラウザで開いた画面からAPIを呼び出す場面が中心になります。このときブラウザは、異なるオリジン(プロトコル・ホスト・ポートの組み合わせ)へのリクエストに対してCORS(Cross-Origin Resource Sharing)と呼ばれる仕組みで通信可否を判定するため、API側で明示的に許可設定を返さないと画面からの呼び出しが失敗します。本章では、CORSエラーを実際に画面から発生させたうえで、Ginのcorsミドルウェアで解消し、単純リクエストとプリフライトリクエストの両方の挙動を確認します。

1.2 ハンズオンの流れ

APIサーバと、それを呼び出す画面(静的HTMLを配信するフロントエンド用サーバ)の2つを立ち上げ、ブラウザからAPIを呼び出したときにCORSエラーが発生することを確認します。そのうえで、Ginのcorsミドルウェアを組み込んでエラーを解消します。

1.3 事前準備

必要なツール

この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。

ツール名 関連箇所 理由
Visual Studio Code Visual Studio Codeのインストール APIサーバ・フロントエンドのコードを記述するエディタとして使用する
Go Goのインストール APIサーバ・フロントエンド用サーバの実行環境として使用する
Webブラウザ CORSエラーとその解消を確認する画面として使用する(Google Chromeなど)

2. CORSエラーの体験

まずは、APIサーバとフロントエンド用サーバを立ち上げ、ブラウザから画面越しにAPIを呼び出したときにCORSエラーが発生することを確認します。ここで実際にエラーの挙動を見ておくと、後続の対策の必要性が具体的に理解できます。

2.1 APIサーバの準備

プロジェクトの作成

任意の場所にcors-hands-onフォルダを作成し、その中にapi-serverフォルダを作成します。Visual Studio Codeの「ファイル」→「フォルダーを開く」からcors-hands-onフォルダを開いてください。以降の操作は、Visual Studio Codeのターミナルから行います。

cors-hands-on/         ← このフォルダを作成
└── api-server/        ← このフォルダを作成

cors-hands-on フォルダで、以下のコマンドを実行してGoがインストールされていることを確認します。

go version

以下のようにGoのバージョンが表示されれば、インストールは確認できています。

go version go1.x.x darwin/arm64

バージョンが表示されない場合は、Goのインストールを先に実施してください。

api-serverフォルダに移動し、モジュールを初期化します。

cd api-server
go mod init api-server

以下のような実行結果が表示されます。

go: creating new go.mod: module api-server

続いて、APIサーバでGinを使うため、Gin本体をgo getコマンドで取得します。

go get github.com/gin-gonic/gin

以下のような実行結果が表示されます(バージョンは環境により変わります)。

go: added github.com/gin-gonic/gin v1.10.0

main.go の作成

Visual Studio Codeのエクスプローラーでapi-serverフォルダを右クリックし、「新しいファイル」を選択してmain.goを作成します。

cors-hands-on/
└── api-server/
    ├── go.mod
    ├── go.sum
    └── main.go       ← このファイルを作成

本章はCORSの動作に集中するため、データベースは使わず、コード内に固定データを持たせる形にします。作成したファイルに以下の内容を記述して保存します。これは、GET /notesでノートの固定データをJSONで返し、PUT /notes/:idで仮の更新レスポンスを返す、最小構成のAPIサーバです。PUTは後半でプリフライトリクエストを確認するときに使います。

package main

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

type Note struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
}

func main() {
	r := gin.Default()

	r.GET("/notes", func(c *gin.Context) {
		notes := []Note{
			{ID: 1, Title: "買い物"},
			{ID: 2, Title: "打ち合わせ資料"},
		}
		c.JSON(http.StatusOK, notes)
	})

	r.PUT("/notes/:id", func(c *gin.Context) {
		id := c.Param("id")
		c.JSON(http.StatusOK, gin.H{"id": id, "status": "updated"})
	})

	r.Run(":8080")
}

動作確認

動作を確認するため、以下のコマンドでAPIサーバを起動します。

go run main.go

以下のような実行結果が表示されます。

[GIN-debug] Listening and serving HTTP on :8080

ポート8080でAPIサーバがリクエスト待ちの状態になりました。動作確認のために、別のターミナルからcurlで叩きます。

curl http://localhost:8080/notes

以下のような実行結果が表示されます。

[{"id":1,"title":"買い物"},{"id":2,"title":"打ち合わせ資料"}]

APIサーバはこのまま起動したままにしておきます。次はフロントエンド側を準備します。

2.2 フロントエンド用サーバの準備

APIをブラウザから呼び出すには、画面を配信するサーバも別に必要です。ここでは、静的なHTMLファイルを配信するだけのGoサーバをfrontendフォルダに用意します。

プロジェクトの作成

Visual Studio Codeのエクスプローラーでcors-hands-onフォルダを右クリックし、「新しいフォルダー」を選択してfrontendという名前でフォルダを作成します。

cors-hands-on/
├── api-server/     ← 起動中のまま
└── frontend/       ← このフォルダを作成

Visual Studio Codeで新しいターミナルを追加し(APIサーバが動いているターミナルは残したまま)、frontendフォルダに移動してからモジュールを初期化します。

cd frontend
go mod init frontend

以下のような実行結果が表示されます。

go: creating new go.mod: module frontend

配信サーバとHTMLの作成

Visual Studio Codeのエクスプローラーでfrontendフォルダを右クリックし、「新しいファイル」を選択してmain.goを作成します。

cors-hands-on/
├── api-server/
│   ├── go.mod
│   ├── go.sum
│   └── main.go
└── frontend/
    ├── go.mod
    └── main.go     ← このファイルを作成

作成したmain.goに以下の内容を記述して保存します。これは、publicフォルダ以下の静的ファイルをhttp://localhost:5173/で配信するだけのサーバです。

package main

import (
	"log"
	"net/http"
)

func main() {
	fs := http.FileServer(http.Dir("./public"))
	log.Println("frontend: http://localhost:5173")
	log.Fatal(http.ListenAndServe(":5173", fs))
}

続いて、配信するHTMLを置くpublicフォルダを作成し、その中にindex.htmlを作成します。

cors-hands-on/
├── api-server/
│   └── ...
└── frontend/
    ├── go.mod
    ├── main.go
    └── public/         ← このフォルダを作成
        └── index.html  ← このファイルを作成

index.htmlに以下の内容を記述して保存します。これは、http://localhost:8080/notesをfetchで呼び出し、結果を画面に表示する最小のHTMLです。本章の主眼はCORSの動作確認なので、HTML/JavaScriptの文法解説は省略します。フロントエンドのコード自体を詳しく知りたい場合は、フロントエンド講座 を参照してください。

<!DOCTYPE html>
<html lang="ja">
<head>
	<meta charset="UTF-8">
	<title>Notes</title>
</head>
<body>
	<h1>Notes</h1>
	<ul id="list"></ul>
	<script>
		fetch("http://localhost:8080/notes")
			.then((res) => res.json())
			.then((notes) => {
				const list = document.getElementById("list");
				for (const note of notes) {
					const li = document.createElement("li");
					li.textContent = `${note.id}: ${note.title}`;
					list.appendChild(li);
				}
			})
			.catch((err) => {
				document.getElementById("list").textContent = "取得に失敗: " + err.message;
			});
	</script>
</body>
</html>

動作確認

ブラウザからアクセスできる画面を用意するため、新しく開いたターミナルで、以下のコマンドでフロントエンド用サーバを起動します。

go run main.go

以下のような実行結果が表示されます。

frontend: http://localhost:5173

ポート5173でフロントエンド用サーバが起動しました。APIサーバ(:8080)とフロントエンド用サーバ(:5173)の両方が起動している状態になっています。

2.3 ブラウザからの呼び出しでのエラー確認

準備が整ったので、Webブラウザでhttp://localhost:5173/を開きます。画面にはNotesの見出しの下に「取得に失敗: Failed to fetch」と表示されます。

画面に表示された「取得に失敗: Failed to fetch」のエラー

続いて、ブラウザの開発者ツールを開きます。Google Chromeであれば、右クリック→「検証」を選ぶか、F12キーで開きます。「Console」タブを見ると、以下のようなエラーが表示されています。

Access to fetch at 'http://localhost:8080/notes' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

開発者ツールのConsoleタブに表示されたCORSエラー

APIサーバ自体はcurlで叩けば200 OKでノートを返せる状態ですが、ブラウザはhttp://localhost:5173のページからhttp://localhost:8080へのリクエストを、オリジンが異なると判断してレスポンスの受け取りをブロックしました。次の見出しで、この挙動の背景にあるCORSの仕組みを整理します。

3. CORSの仕組み

エラーの解消に入る前に、なぜブラウザがこのような挙動をするのかを整理します。CORSの前提となるSame-Origin Policy、CORSが解決する範囲、そしてプリフライトリクエストの役割を順に確認します。

3.1 オリジンとは

CORSの話に入る前に、そもそもオリジン(Origin)が何を指すのかを押さえておきます。オリジンは、Webにおいてリソースの「出どころ」を識別する単位で、プロトコル・ホスト・ポートの3つの組み合わせで決まります。1つでも違うオリジンは別のオリジンとして扱われます。

URL オリジン
http://localhost:5173/index.html http://localhost:5173
http://localhost:8080/notes http://localhost:8080
https://example.com/api/users https://example.com
http://api.example.com/users http://api.example.com

先ほど体験したケースでは、フロントエンドはhttp://localhost:5173、APIはhttp://localhost:8080で、ポート番号が違うため別オリジンとして扱われます。Webブラウザは、この「オリジンが異なるかどうか」を基準にセキュリティ制約を適用します。

3.2 Same-Origin PolicyとCORS

Same-Origin Policy(同一オリジンポリシー)は、Webブラウザが「あるオリジンで読み込まれたページから、別のオリジンのリソースへ自由にアクセスできないようにする」ためのセキュリティ制約です。ここでのオリジンは、プロトコル・ホスト・ポートの3つの組み合わせで決まります。1つでも異なれば別オリジンとして扱われます。

ページのオリジン APIのオリジン 同一オリジンか
http://localhost:5173 http://localhost:5173/api 同一オリジンである
http://localhost:5173 http://localhost:8080 別オリジンである(ポートが違う)
http://example.com https://example.com 別オリジンである(プロトコルが違う)
http://example.com http://api.example.com 別オリジンである(ホストが違う)

Same-Origin Policyだけだと、別オリジンのAPIサーバをフロントエンドから呼び出す構成が成立しません。そこで、サーバ側から「このオリジンからのアクセスは許可します」と明示的に宣言できる仕組みが必要になります。それがCORS(Cross-Origin Resource Sharing)です。

CORSでは、サーバがレスポンスにAccess-Control-Allow-Originなどの専用ヘッダを付けることで、ブラウザに対して「このオリジンからのクロスオリジンリクエストは許可する」と伝えます。ブラウザはこのヘッダを見て、リクエスト結果のスクリプトへの受け渡しを許可するかどうかを判定します。

📝 CORSの詳細仕様
CORSの詳細な仕様は、Cross-Origin Resource Sharing (CORS)(MDN Web Docs)に記載があります。

3.3 単純リクエストとプリフライトリクエスト

CORSは、リクエストの内容によって挙動が変わります。ブラウザから送られるクロスオリジンリクエストは、大きく単純リクエストプリフライトが必要なリクエストの2種類に分かれます。

単純リクエストは、以下のような条件を満たすリクエストです。

  • メソッドがGETHEADPOSTのいずれか
  • Content-Typetext/plainapplication/x-www-form-urlencodedmultipart/form-dataのいずれか
  • カスタムヘッダ(Authorizationなど)を付けていない

これらの条件に該当する場合、ブラウザはリクエストをそのまま送信し、レスポンスのAccess-Control-Allow-Originヘッダを見て許可判定を行います。先ほど体験したGET /notesは単純リクエストに該当し、レスポンスにAccess-Control-Allow-Originが無かったためブラウザがブロックしました。

プリフライトが必要なリクエストは、PUTDELETEAuthorizationヘッダ付き・Content-Type: application/jsonなど、単純リクエストの条件から外れるリクエストです。これらは従来のHTMLフォームでは送信できない形式で、リソースの更新・削除といったサーバ側の状態を変える操作や、任意のヘッダを付けたリクエストが該当します。もしチェックなしでブラウザが送信を許してしまうと、悪意のあるサイトから許可なく送られたリクエストによってサーバ側のデータが書き換えられたり、認証情報付きのリクエストが偽装されたりする恐れがあります。

そこでブラウザは、本来のリクエストを送る前に、OPTIONSメソッドでプリフライトリクエストを送り、「これから送るメソッド・ヘッダは許可されているか」をサーバに問い合わせる形にしています。単純リクエストがCORS登場以前から送れていた(HTMLフォームで送れる範囲の)操作でそのまま送信を許すのに対し、これらのリクエストは「先に許可を取ってから送る」という一段強い制約を敷くことで、意図しないクロスオリジンの書き込み・認証利用を防いでいます。

sequenceDiagram
    participant B as ブラウザ
    participant S as APIサーバ
    B->>S: OPTIONS /notes<br>Access-Control-Request-Method: PUT
    S-->>B: 200 OK<br>Access-Control-Allow-Origin: http://localhost:5173<br>Access-Control-Allow-Methods: PUT
    B->>S: PUT /notes<br>本来のリクエスト
    S-->>B: 200 OK<br>Access-Control-Allow-Origin: http://localhost:5173

サーバがプリフライトに対して許可を返せば、ブラウザは本来のリクエストを送信します。許可されないヘッダやメソッドが含まれていれば、本来のリクエスト自体が送信されずにブロックされます。

📝 CORSはブラウザの仕組み
CORSはあくまでブラウザが実装している安全装置であり、APIサーバ自体を保護するものではありません。curlやサーバ間の通信ではCORSヘッダの有無にかかわらずリクエストは通ります。APIサーバのアクセス制御は、CORSとは別に認証・認可の仕組みで守る必要があります。

4. Ginのcorsミドルウェアによる対応

CORSの仕組みが分かったところで、APIサーバにAccess-Control-Allow-Originヘッダを返す設定を組み込みます。Ginでは、gin-contrib/corsというミドルウェアが公式に提供されており、これをr.Useで登録することで実現できます。

4.1 ミドルウェアの導入

ミドルウェアの追加

APIサーバを開いたターミナルで、動作中のサーバをいったんCtrl + Cで停止します。停止したら、CORSヘッダの付与とプリフライト応答を任せるため、api-serverフォルダで以下のコマンドを実行し、gin-contrib/corsを導入します。

go get github.com/gin-contrib/cors

以下のような実行結果が表示されます(バージョンは環境により変わります)。

go: added github.com/gin-contrib/cors v1.7.2

api-server/main.goを以下のように書き換えて保存します。これは、http://localhost:5173からのアクセスを許可するCORSミドルウェアを組み込んだAPIサーバです。GET /notesPUT /notes/:idのルートは変えずに、r.Use(cors.New(...)) の1行をr := gin.Default()の直後に追加した形になります。

package main

import (
	"net/http"

	"github.com/gin-contrib/cors"
	"github.com/gin-gonic/gin"
)

type Note struct {
	ID    int    `json:"id"`
	Title string `json:"title"`
}

func main() {
	r := gin.Default()

	r.Use(cors.New(cors.Config{
		AllowOrigins: []string{"http://localhost:5173"},
		AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
		AllowHeaders: []string{"Content-Type", "Authorization"},
	}))

	r.GET("/notes", func(c *gin.Context) {
		notes := []Note{
			{ID: 1, Title: "買い物"},
			{ID: 2, Title: "打ち合わせ資料"},
		}
		c.JSON(http.StatusOK, notes)
	})

	r.PUT("/notes/:id", func(c *gin.Context) {
		id := c.Param("id")
		c.JSON(http.StatusOK, gin.H{"id": id, "status": "updated"})
	})

	r.Run(":8080")
}

動作確認

変更を反映するため、以下のコマンドでAPIサーバを再起動します。

go run main.go

以下のような実行結果が表示されます。

[GIN-debug] Listening and serving HTTP on :8080

ブラウザに戻って、http://localhost:5173/を再読み込みしてください。画面に以下のようにノート一覧が表示されれば、CORS対応は成功しています。

Notes
  ・ 1: 買い物
  ・ 2: 打ち合わせ資料

レスポンスヘッダにAccess-Control-Allow-Origin: http://localhost:5173が実際に付いていることは、別のターミナルから curl で確認できます。ブラウザからの呼び出しを再現するため、-H "Origin: http://localhost:5173" でリクエスト元のオリジンを付けて送り、-i オプションでレスポンスヘッダも表示します。

curl -i -H "Origin: http://localhost:5173" http://localhost:8080/notes

以下のような実行結果が表示されます。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
Content-Type: application/json; charset=utf-8
Date: XXX, XX XXX XXXX XX:XX:XX GMT
Content-Length: 61

[{"id":1,"title":"買い物"},{"id":2,"title":"打ち合わせ資料"}]

Access-Control-Allow-Origin: http://localhost:5173 の行があれば、CORSヘッダが正しく付与されています。逆に -H "Origin: ..." を付けずに叩くと、ブラウザ以外からの通常のリクエスト扱いになり、このヘッダは付きません(ミドルウェアがオリジン判定して付与するため)。

コードの解説

r.Use(cors.New(cors.Config{
    AllowOrigins: []string{"http://localhost:5173"},
    AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"},
    AllowHeaders: []string{"Content-Type", "Authorization"},
}))

cors.Newcors.Configを受け取って、gin.HandlerFunc型のミドルウェアを返します。r.Useで登録すると、以降に登録される全ルートに対してCORSヘッダの付与とプリフライト(OPTIONS)の応答を自動で行うようになります。

AllowOrigins: []string{"http://localhost:5173"}

CORSで許可するオリジンを列挙します。ここに含まれるオリジンからのリクエストに対してのみ、Access-Control-Allow-Originヘッダが付与されます。複数のオリジンをスライスで指定できます。

AllowMethods: []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}

プリフライトリクエストで許可するHTTPメソッドです。OPTIONSはプリフライト自身のメソッドで、通常は明示的に含めておきます。

AllowHeaders: []string{"Content-Type", "Authorization"}

リクエストで許可するヘッダを列挙します。Content-Type: application/jsonでJSONを送る場合や、Authorizationヘッダでトークンを送る場合はここに含めておく必要があります。

⚠️ 「Failed to fetch」がブラウザで解消しない場合
ブラウザは一度受け取ったレスポンス(CORS判定を含む)をキャッシュする場合があります。ミドルウェアを追加したのに画面上でエラーが続く場合は、ブラウザの再読み込みを「スーパーリロード」(ChromeではShift + F5またはCtrl + Shift + RCmd + Shift + R)で行い、開発者ツールを開いた状態で「Network」タブのDisable cacheにチェックを入れて再度確認してください。

4.2 プリフライトリクエストの確認

これまで確認してきたGET /notesは「単純リクエスト」に該当するため、ブラウザはOPTIONSを送らずにGETをそのまま投げていました。ここでは、単純リクエストの条件から外れるPUT /notes/:idmain.goに登録済み)に対して、プリフライト(OPTIONS)が実際に発生することをcurlで確認します。

プリフライトリクエストの再現

OPTIONSリクエストにプリフライト固有のヘッダを付けて送ります。書き方は以下のとおりです。

curl -X OPTIONS -i \
  -H "Origin: 呼び出し元のオリジン" \
  -H "Access-Control-Request-Method: 本来送るメソッド" \
  -H "Access-Control-Request-Headers: 本来送るヘッダ" \
  URL

上記の書式に沿って、本来送りたいメソッドPUTと、本来付けたいヘッダContent-Typeを伝えるプリフライトを、別のターミナルから送ります。

curl -X OPTIONS -i \
  -H "Origin: http://localhost:5173" \
  -H "Access-Control-Request-Method: PUT" \
  -H "Access-Control-Request-Headers: Content-Type" \
  http://localhost:8080/notes/1

以下のような実行結果が表示されます。

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: http://localhost:5173
Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type,Authorization
Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

Access-Control-Allow-MethodsPUTが、Access-Control-Allow-HeadersContent-Typeが含まれています。ブラウザはこの応答を見て「PUTとContent-Typeヘッダは許可されている」と判断し、続けて本来のPUTリクエストを送ります。

コードを解説します。

-H "Origin: http://localhost:5173"

呼び出し元のオリジンをサーバに伝えるヘッダです。ブラウザはクロスオリジンリクエストの際に自動でこれを付けますが、curlでは明示的に指定します。

-H "Access-Control-Request-Method: PUT"

本来これから送るリクエストのHTTPメソッドを、プリフライトの段階でサーバに事前通知します。サーバはこれとAllowMethodsを突き合わせて許可可否を判定します。

-H "Access-Control-Request-Headers: Content-Type"

本来これから送るリクエストに含める非単純リクエスト用のヘッダを、プリフライトの段階でサーバに事前通知します。サーバはこれとAllowHeadersを突き合わせて許可可否を判定します。

HTTP/1.1 204 No Content

プリフライトへの応答は本文を持たない204 No Contentで返すのが一般的です。

Access-Control-Allow-Methods: GET,POST,PUT,DELETE,OPTIONS
Access-Control-Allow-Headers: Content-Type,Authorization

サーバが許可するメソッドとヘッダを列挙するレスポンスヘッダです。gin-contrib/corscors.ConfigAllowMethodsAllowHeadersの値をもとに自動で組み立てます。

Vary: Origin
Vary: Access-Control-Request-Method
Vary: Access-Control-Request-Headers

「同じURLでも、これらのリクエストヘッダの内容が変わるとレスポンスが変わる」ことをブラウザやプロキシに伝えるヘッダです。CORSレスポンスは呼び出し元のオリジンなどに応じて変わるため、Varyでキャッシュの取り違えを防ぎます。

本来のリクエストの送信

プリフライトが通ったあと、ブラウザは本来送りたいリクエストを続けて投げます。この段階もcurlで再現します。書き方は以下のとおりです。

curl -X メソッド -i \
  -H "Origin: 呼び出し元のオリジン" \
  -H "Content-Type: application/json" \
  -d 'JSON文字列' \
  URL

上記の書式に沿って、PUT /notes/1にJSONを送ります。

curl -X PUT -i \
  -H "Origin: http://localhost:5173" \
  -H "Content-Type: application/json" \
  -d '{"title":"更新済み"}' \
  http://localhost:8080/notes/1

以下のような実行結果が表示されます。

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://localhost:5173
Content-Type: application/json; charset=utf-8
Date: XXX, XX XXX XXXX XX:XX:XX GMT
Content-Length: 27

{"id":"1","status":"updated"}

ブラウザ経由の場合、OPTIONSPUTの2ステップが自動的に行われます。プリフライトが失敗した場合(Access-Control-Allow-Methodsに対象メソッドが含まれない、など)は、本来のPUTリクエストは送信すらされずにブロックされる点が、単純リクエストとの違いです。gin-contrib/corsミドルウェアは、AllowMethodsAllowHeadersに含まれる範囲であれば、OPTIONSへの応答を自動で組み立てます。

AllowOrigins に * を使うことの是非

gin-contrib/corsでは、すべてのオリジンを許可するAllowAllOrigins: trueという設定も用意されていますが、以下の理由から利用には注意が必要です。

  • 悪意のある第三者のサイトからも、そのAPIをブラウザ経由で呼び出せる状態になる
  • Cookieによる認証(AllowCredentials: true)と*は同時に使えず、ブラウザにブロックされる

原則としては、AllowOrigins明示的なオリジンを列挙する運用が安全です。開発時に一時的に緩めたいだけであっても、AllowAllOrigins: trueはデフォルトの選択肢にはしません。

💡 ポイント
本章では AllowOrigins にオリジンをハードコードしていますが、実際には開発環境・ステージング・本番環境で許可するオリジンが変わります。運用では環境変数や .env ファイル(Go標準ライブラリ 参照)からオリジンを読み込み、コードを書き換えずに切り替えられる形にするのが一般的です。
📝 CORSと認証の関係
CookieやAuthorizationヘッダを伴うクロスオリジンリクエストを許可するには、AllowCredentials: trueと、AllowOriginsに具体的なオリジンを指定する必要があります(AllowAllOrigins: trueは同時に使えない)。次章の GoでJWT認証を実装しようAuthorizationヘッダによる認証を扱う際、この組み合わせが前提となります。

5. 不要リソースの削除

ハンズオンで起動した2つのサーバは、Ctrl + Cで停止すればリソースが残ることはありません。フォルダ(cors-hands-on/)はそのまま残しておいても支障ありませんが、不要であれば削除してください。

6. まとめ

この章では、CORSの仕組みを学びつつ、実際にGinのcorsミドルウェアでブラウザからのクロスオリジン呼び出しを許可する実装を体験しました。

  • Same-Origin Policyにより、ブラウザは異なるオリジンへのfetch結果をデフォルトでブロックする
  • オリジンは、プロトコル・ホスト・ポートの3つの組み合わせで決まる
  • CORSは、サーバがAccess-Control-Allow-Originなどのヘッダで「このオリジンからのアクセスを許可する」と明示する仕組みである
  • プリフライトが必要なリクエストでは、ブラウザが本リクエスト前にOPTIONSメソッドで許可を問い合わせる
  • CORSはブラウザの安全装置であり、curlやサーバ間通信には効かない。APIサーバのアクセス制御は認証・認可で別途行う
  • Ginではgin-contrib/corsミドルウェアをr.Useで登録することで、CORSヘッダとプリフライト応答を自動化できる
  • AllowOriginsに許可オリジンを明示的に列挙する運用が基本であり、AllowAllOrigins: trueは原則使わない
  • PUTDELETEContent-Type: application/jsonなどを含むリクエストではプリフライト(OPTIONS)が先に飛び、gin-contrib/corsはこれにも自動応答する

次の章では、GoでJWT認証を実装する流れをハンズオン形式で学びつつ、ログインAPIからトークン検証・失効管理までを体験します。

この教材は役に立ちましたか?

いいねをたくさんいただけると、制作者の励みになり、より多くのセクションが作れるようになります。

感想を一言(任意)

いただいたコメントは次の制作のヒントになります。ぜひお気軽にご投稿ください。

このコメントは他の受講生には公開されません。DevOps Camp運営が、教材改善のために確認します。

0 / 2000