ToDoアプリの作成（Middleware(CORS, CSRF)編）

<h2>はじめに</h2>
今回で最終回になります。<br>
今回は，前回に続き、CSRFトークンを使った認証の実装をしていきます。

https://qiita.com/sawadashinji0526/items/3850733f048d5c353f4f

<h2>routerの実装</h2>
routerにCORSのmiddlewareを追加していきます。

```router/router.go
package router

import (
	"go-rest-api/controller"
	"net/http"
	"os"

	echojwt "github.com/labstack/echo-jwt/v4"
   
	"github.com/labstack/echo/v4"
 //v4に変更
 "github.com/labstack/echo/v4/middleware"
)

func NewRouter(uc controller.IUserController, tc controller.ITaskController) *echo.Echo {
	e := echo.New()
	//ここからが今回追加したCORSのMiddleware
	e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
		 //フロントエンドのアプリケーションにてCORSが許可するオリジン（ドメイン）を指定します。
		 //http://localhost:3000 はローカル開発環境のオリジンです。(今回はフロントの開発は割愛します)
		AllowOrigins: []string{"http://localhost:3000", os.Getenv("FE_URL")},
		//CORSが許可するHTTPヘッダーを指定します。echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept はヘッダーです。echo.HeaderAccessControlAllowHeaders は、クライアントがリクエストに含めることができるヘッダーをサーバーが指定するために使用されます。echo.HeaderXCSRFToken はCSRFトークンを含むヘッダーです。
		AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept,
			echo.HeaderAccessControlAllowHeaders, echo.HeaderXCSRFToken},
        //GET, PUT, POST, DELETE は一般的なHTTPメソッドです。必要に応じてメソッドを追加します。
		AllowMethods:     []string{"GET", "PUT", "POST", "DELETE"},
        //trueに設定することで、Cookieなどの認証情報を含むリクエストを許可します。これは、認証が必要なAPIエンドポイントで重要です。
		AllowCredentials: true,
	}))
	//ここまでがCORSの設定
//ここからが今回追加したCSRFのMiddleware
e.Use(middleware.CSRFWithConfig(middleware.CSRFConfig{
		//CSRFトークンを含むCookieのパスを指定します。/ はルートパス全体を意味します。
		CookiePath:     "/",
        //CSRFトークンを含むCookieのドメインを指定します。os.Getenv("API_DOMAIN") は、環境変数API_DOMAINからAPIのドメイン名を取得します。
		CookieDomain:   os.Getenv("API_DOMAIN"),
        //trueに設定することで、JavaScriptからCookieにアクセスできないようにします。クライアントサイドのJavaScriptからCookieが読み取れないようにすることで、攻撃者がユーザーのセッションを盗むことを困難にします。
		CookieHTTPOnly: true,
        //SameSite属性を設定します。http.SameSiteDefaultMode は、Laxモードと同じ意味で、サードパーティのコンテキストからのリクエストではCookieが送信されないようにします。これは、CSRF攻撃を防ぐために重要です。
		CookieSameSite: http.SameSiteDefaultMode,
	}))
    //ここまでがCSRFの設定
    
	e.POST("/signup", uc.SignUp)
	e.POST("/login", uc.Login)
	e.POST("/logout", uc.LogOut)
    //csrfのエンドポイントにリクエストが合った際はUserControllerのCsrfTokenのメソッドを呼び出す。（今回追加したコード）
	e.GET("/csrf", uc.CsrfToken)
	t := e.Group("/tasks")
	t.Use(echojwt.WithConfig(echojwt.Config{
		SigningKey:  []byte(os.Getenv("SECRET")),
		TokenLookup: "cookie:token",
	}))
	t.GET("", tc.GetAllTasks)
	t.GET("/:taskId", tc.GetTaskById)
	t.POST("", tc.CreateTask)
	t.PUT("/:taskId", tc.UpdateTask)
	t.DELETE("/:taskId", tc.DeleteTask)
	return e
}
```

<h2>ユーザーコントローラの実装</h2>
CSRFトークンを取得するためのメソッドを追加していきます。

```controller/user_controller.go
package controller

import (
	"go-rest-api/model"
	"go-rest-api/usecase"
	"net/http"
	"os"
	"time"

	"github.com/labstack/echo/v4"
)

type IUserController interface {
	SignUp(c echo.Context) error
	Login(c echo.Context) error
	LogOut(c echo.Context) error
    //下記のコードを追加
	CsrfToken(c echo.Context) error
}

type userController struct {
	uu usecase.IUserUsecase
}

func NewUserController(uu usecase.IUserUsecase) IUserController {
	return &userController{uu}
}

func (uc *userController) SignUp(c echo.Context) error {
	user := model.User{}
	if err := c.Bind(&user); err != nil {
		return c.JSON(http.StatusBadRequest, err.Error())
	}
	userRes, err := uc.uu.SignUp(user)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}
	return c.JSON(http.StatusCreated, userRes)
}

func (uc *userController) Login(c echo.Context) error {
	user := model.User{}
	if err := c.Bind(&user); err != nil {
		return c.JSON(http.StatusBadRequest, err.Error())
	}
	tokenString, err := uc.uu.Login(user)
	if err != nil {
		return c.JSON(http.StatusInternalServerError, err.Error())
	}
	cookie := new(http.Cookie)
	cookie.Name = "token"
	cookie.Value = tokenString
	cookie.Expires = time.Now().Add(24 * time.Hour)
	cookie.Path = "/"
	cookie.Domain = os.Getenv("API_DOMAIN")
	//cookie.Secure = true
	cookie.HttpOnly = true
	cookie.SameSite = http.SameSiteNoneMode
	c.SetCookie(cookie)
	return c.NoContent(http.StatusOK)
}

func (uc *userController) LogOut(c echo.Context) error {
	cookie := new(http.Cookie)
	cookie.Name = "token"
	cookie.Value = ""
	cookie.Expires = time.Now()
	cookie.Path = "/"
	cookie.Domain = os.Getenv("API_DOMAIN")
	//cookie.Secure = true
	cookie.HttpOnly = true
	cookie.SameSite = http.SameSiteNoneMode
	c.SetCookie(cookie)
	return c.NoContent(http.StatusOK)
}

func (uc *userController) CsrfToken(c echo.Context) error {
    //Echoのコンテキスト c から "csrf" というキーで保存されている値を取得します。この値は、通常、CSRFミドルウェアによって事前にコンテキストに設定されているCSRFトークンです。
	token := c.Get("csrf").(string)
    //Echoのコンテキストを使用して、JSON形式のレスポンスをクライアントに返します。
	return c.JSON(http.StatusOK, echo.Map{
		"csrf_token": token,
	})
}

```

<h2>動作確認</h2>
下記のコマンドでプログラムを起動していきます。

`GO_ENV=dev go run main.go`

![スクリーンショット 2024-11-19 19.27.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/8ea5a371-33f8-6af8-695b-d04d1b50eb75.png)

今回もPostmanで動作確認していきます。
`http://localhost:8080/login`のエンドポイントを入力します。
メソッドはPOSTを選択し、Bodyのところはrawを選択し、JSONを選択します。
emailとpasswordを入力しSendボタンを押下します。

![スクリーンショット 2024-11-19 19.29.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/04571c20-8c4c-63c5-42e7-cfc021787b03.png)

そうすると`"message": "missing csrf token in request header"`とメッセージが表示されます。

次はCSRFトークンを取得していきます。
`http://localhost:8080/csrf`のエンドポイントを入力し、GETメソッドでCSRFトークンを取得します。

![スクリーンショット 2024-11-19 19.34.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/2b4abb5f-db9d-42b8-6993-bce9ade19640.png)
`"csrf_token": "AsqnsTlDnmSkmRCJIfzEXEXTxLrqNEjM"`と書かれてるのがCSRFトークンです。

CSRFトークンをコピーして、HeadersタブのKey`X-CSRF-TOKEN`のValueにコピーしたCSRFトークンを貼り付けます。

![スクリーンショット 2024-11-19 19.38.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/ff2aa4e5-d743-b896-830a-5e1f5122723d.png)

`http://localhost:8080/login`のエンドポイントを入力しPOSTメソッドでアクセスしていきます。

![スクリーンショット 2024-11-19 19.41.12.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/748ca1e8-140c-f7a4-ed4a-618077a1e2b3.png)

ステータスが`200 OK`となっていて、Cookiesを見るとcsrfという名前でCookiesにcsrfトークンが保存されているのを確認できます。

![スクリーンショット 2024-11-19 19.44.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/dc76e613-e68c-4d21-ecb8-13b5497fbc24.png)

次はPOSTメソッドで`http://localhost:8080/tasks`のエンドポイントにtitleをtask1にしてアクセスしていきます。

![スクリーンショット 2024-11-19 19.50.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/80381788-98ef-6876-cb8f-22c22803682d.png)

そうすると`"message": "missing csrf token in request header"`と表示されます。

前回取得したCSRFトークンをコピーし、Headersに移動し、Keyに`X-CSRF-TOKEN`を入力、Valueに先ほど取得したCSRFトークンを貼り付け、再度`http://localhost:8080/tasks`のエンドポイントにtitleをtask1にしてアクセスしていくと新しくタスクが登録されます。

![スクリーンショット 2024-11-19 19.57.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3628257/5de93f2b-52f8-c3ae-9661-064fd311f140.png)

<h2>まとめ</h2>
5回にわたり、ToDoアプリの作成を行ってきました。<br>
12月からアドベントカレンダーが始まるので、引き続きインプットしたものを技術ブログに書いていきます。