0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

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

Posted at

はじめに

今回で最終回になります。
今回は,前回に続き、CSRFトークンを使った認証の実装をしていきます。

routerの実装

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
}

ユーザーコントローラの実装

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,
	})
}

動作確認

下記のコマンドでプログラムを起動していきます。

GO_ENV=dev go run main.go

スクリーンショット 2024-11-19 19.27.37.png

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

スクリーンショット 2024-11-19 19.29.27.png

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

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

スクリーンショット 2024-11-19 19.34.09.png
"csrf_token": "AsqnsTlDnmSkmRCJIfzEXEXTxLrqNEjM"と書かれてるのがCSRFトークンです。

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

スクリーンショット 2024-11-19 19.38.43.png

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

スクリーンショット 2024-11-19 19.41.12.png

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

スクリーンショット 2024-11-19 19.44.44.png

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

スクリーンショット 2024-11-19 19.50.17.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

まとめ

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

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?