4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

gorilla/csrfのオリジン間APIの実装とCORSエラー

Last updated at Posted at 2022-01-30

バックエンドをGoでフロントエンドをReactでアプリを作成しており、CSRF攻撃の対策のためgorilla/csrfを導入しようとしたのですが、CORS関係でエラーが頻発しました。
この記事では、Reactは使わず、goで立ち上げた仮想フロントエンドサーバーにaxiosを入れて解説します。

gorilla/csrfライブラリの公式
https://github.com/gorilla/csrf#javascript-applications

実装結果は下記の通りです。

ファイル構成
.
├── back
│   └── main.go
├── front
│   ├── index.html
│   └── main.go
├── go.mod
└── go.sum
front/main.go
package main

import (
	"log"
	"net/http"
	"text/template"
)

func main() {
	http.HandleFunc("/", func(w http.ResponseWriter, _ *http.Request) {
		t, _ := template.ParseFiles("index.html")
		t.Execute(w, nil)
	})

	log.Fatal(http.ListenAndServe(":3000", nil))
}
front/index.html
<!DOCTYPE html>
<head>
  <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
  <button onclick="submit()">Post Data</button>
  <script>
    async function submit() {
      const instance = axios.create({
        baseURL: "http://localhost:8000/api",
        withCredentials: true, // Cookieのやりとりをオンにする
      })

      // トークンを取得
      const response = await instance.get("/token")

      // 受け取ったトークンをX-CSRF-Tokenヘッダーに付ける
      instance.defaults.headers.post["X-CSRF-Token"] = response.headers["x-csrf-token"]
      instance
        .post("/post", new URLSearchParams({say: "The World! Stop time!"}))
        .then(res => alert(res.data))
    }
  </script>  
</body>
</html>
back/main.go
package main

import (
	"fmt"
	"net/http"

	"github.com/gorilla/csrf"
	"github.com/gorilla/mux"
)

func main() {
	r := mux.NewRouter()
	csrfMiddleware := csrf.Protect([]byte("32-byte-long-auth-key"))

	api := r.PathPrefix("/api").Subrouter()
	api.Use(csrfMiddleware)
	api.HandleFunc("/token", token).Methods(http.MethodGet)
	api.HandleFunc("/post", post).Methods(http.MethodOptions) // プリフライトを受け付ける
	api.HandleFunc("/post", post).Methods(http.MethodPost)

	http.ListenAndServe(":8000", r)
}

func token(w http.ResponseWriter, r *http.Request) {
	// オリジン間リソース共有と言ったらこれが無いと始まらないよね
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
	// X-CSRF-Tokenをフロント側で受け取れるようにする
	w.Header().Set("Access-Control-Expose-Headers", "X-CSRF-Token")
	// フロント側のブラウザにクッキーがセットされるようにする
	w.Header().Set("Access-Control-Allow-Credentials", "true")
	// トークンをセット
	w.Header().Set("X-CSRF-Token", csrf.Token(r))
}

func post(w http.ResponseWriter, r *http.Request) {
	// オリジン間リソース共有と言ったらこれが無いと始まらないよね
	w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
	// 受け入れるヘッダーを指定。特にX-CSRF-Tokenは忘れずに。
	w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-CSRF-Token")
	// プリフライトのときにPOSTメソッド許可を返す。
	w.Header().Set("Access-Control-Allow-Methods", "POST")
	// Cookieを受け入れる。
	w.Header().Set("Access-Control-Allow-Credentials", "true")

	fmt.Fprint(w, r.FormValue("say"))
}

動作確認

zsh
$ go run front/main.go
$ go run back/main.go

http://local:3000
をブラウザで開いて、ボタン押してコンソールエラーが無ければ、OKです。
てsってst.png

以上です。

4
2
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
4
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?