はじめに
今回で最終回になります。今回は,前回に続き、CSRFトークンを使った認証の実装をしていきます。
routerの実装
routerにCORSのmiddlewareを追加していきます。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トークンを取得するためのメソッドを追加していきます。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
今回もPostmanで動作確認していきます。
http://localhost:8080/login
のエンドポイントを入力します。
メソッドはPOSTを選択し、Bodyのところはrawを選択し、JSONを選択します。
emailとpasswordを入力しSendボタンを押下します。
そうすると"message": "missing csrf token in request header"
とメッセージが表示されます。
次はCSRFトークンを取得していきます。
http://localhost:8080/csrf
のエンドポイントを入力し、GETメソッドでCSRFトークンを取得します。
"csrf_token": "AsqnsTlDnmSkmRCJIfzEXEXTxLrqNEjM"
と書かれてるのがCSRFトークンです。
CSRFトークンをコピーして、HeadersタブのKeyX-CSRF-TOKEN
のValueにコピーしたCSRFトークンを貼り付けます。
http://localhost:8080/login
のエンドポイントを入力しPOSTメソッドでアクセスしていきます。
ステータスが200 OK
となっていて、Cookiesを見るとcsrfという名前でCookiesにcsrfトークンが保存されているのを確認できます。
次はPOSTメソッドでhttp://localhost:8080/tasks
のエンドポイントにtitleをtask1にしてアクセスしていきます。
そうすると"message": "missing csrf token in request header"
と表示されます。
前回取得したCSRFトークンをコピーし、Headersに移動し、KeyにX-CSRF-TOKEN
を入力、Valueに先ほど取得したCSRFトークンを貼り付け、再度http://localhost:8080/tasks
のエンドポイントにtitleをtask1にしてアクセスしていくと新しくタスクが登録されます。
まとめ
5回にわたり、ToDoアプリの作成を行ってきました。12月からアドベントカレンダーが始まるので、引き続きインプットしたものを技術ブログに書いていきます。