[Go]Go製APIサーバで認証処理を作成する

## はじめに
Go、Ginを使って認証処理を実装していきます
認証情報はCookieを使ってセッションに保持します

## 環境
Golang 1.16.4
Gin 1.7.4

## リクエスト用のモデルを作成します
メールアドレスとパスワードで認証していきます

```login.go
type Login struct {
	Email     string    `json:"email" bson:"email"`
	Password  string    `json:"password" bson:"password"`
	UpdatedAt time.Time `json:"updated_at"`
	CreatedAt time.Time `json:"created_at"`
}
```

## ログイン用のhandler

```login_handler.go
type LoginHandler interface {
	Login(ctx *gin.Context)
}

type loginHandler struct {
	loginUsecase loginUsecase.LoginUsecase
}

// NewLoginHandler constructor
func NewLoginHandler(loginUsecase loginUsecase.LoginUsecase) LoginHandler {
	return &loginHandler{loginUsecase: loginUsecase}
}

func (loginHandler *loginHandler) Login(ctx *gin.Context) {
	var request domain.Login
	err := ctx.BindJSON(&request)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, delivery.NewH(http.StatusText(http.StatusBadRequest), http.StatusBadRequest))
	} else {
		// メールアドレスでユーザ情報取得
		user, err := loginHandler.loginUsecase.GetByEmail(request.Email)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, delivery.NewH(err.Error(), nil))
			return
		}

		// ハッシュ値でのパスワード比較
		err = bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(request.Password))
		if err != nil {
			ctx.JSON(http.StatusBadRequest, delivery.NewH(err.Error(), http.StatusBadRequest))
		} else {
			session := sessions.Default(ctx)
			// セッションに格納する為にユーザ情報をJson化
			loginUser, err := json.Marshal(user)
			if err == nil {
				u, _ := uuid.NewRandom()
				accessToken := u.String()
				session.Set(accessToken, string(loginUser))
				session.Save()
				ctx.SetSameSite(http.SameSiteDefaultMode)

				ctx.JSON(http.StatusOK, delivery.NewH(http.StatusText(http.StatusOK), accessToken))
			} else {
				ctx.JSON(http.StatusInternalServerError, delivery.NewH(err.Error(), http.StatusInternalServerError))
			}
		}
	}
}
```

## ミドルウェア
認証ガード用のミドルウェアを作成していきます

```auth_middleware.go
func LoginCheckMiddleware() gin.HandlerFunc {
	return func(ctx *gin.Context) {
		accessToken := ctx.Request.Header.Get("accessToken")
		session := sessions.Default(ctx)
		// Json文字列がinterdace型で格納されている。dproxyのライブラリを使用して値を取り出す
		loginUserJson, err := dproxy.New(session.Get(accessToken)).String()

		if err != nil {
			ctx.Status(http.StatusUnauthorized)
			ctx.Abort()
		} else {
			var loginInfo domain.User
			// Json文字列のアンマーシャル
			err := json.Unmarshal([]byte(loginUserJson), &loginInfo)
			if err != nil {
				ctx.Status(http.StatusUnauthorized)
				ctx.Abort()
			} else {
				ctx.Next()
			}
		}
	}
}
```

## ルーティングの設定
CORSの設定をしていきます
OriginをまたいだXMLHttpRequestでCookie を送りたい場合、
Cookieの送受信を許可するために、クライアントサイド・サーバーサイドに実装が必要になります

CORSに関して
https://qiita.com/att55/items/2154a8aad8bf1409db2b

また、` authUserGroup := r.Gin.Group("/auth")`以下で、
GinのGroup機能を使って、認証ガードをかけています


```routing.go
func NewRouting(db *DB) *Routing {
	r := &Routing{
		DB:  db,
		Gin: gin.Default(),
	}
	// Corsの設定
	r.Gin.Use(cors.New(cors.Config{
		// 許可アクセス元
		AllowOrigins: []string{
			"https://hogehoge.com",
		},
		// アクセス許可HTTPメソッド(以下PUT,DELETEアクセス不可)
		AllowMethods: []string{
			"POST",
			"GET",
			"OPTIONS",
		},
		// 許可HTTPリクエストヘッダ
		AllowHeaders: []string{
			"Access-Control-Allow-Credentials",
			"Access-Control-Allow-Headers",
			"Content-Type",
			"Content-Length",
			"Accept-Encoding",
			"Authorization",
			"accessToken",
			"Set-Cookie",
			"Cookie",
		},
		// cookie必要許可
		AllowCredentials: true,
		// preflightリクエストの結果をキャッシュする時間
		MaxAge: 24 * time.Hour,
	}))

	// セッションCookieの設定
	store := cookie.NewStore([]byte("secret"))
	store.Options(sessions.Options{
		Secure:   false,
		HttpOnly: false})
	r.Gin.Use(sessions.Sessions("sampleAppSessKey", store))

	r.setRouting()
	return r
}

func (r *Routing) setRouting() {
    // 認証済のみアクセス可能なグループ
	authUserGroup := r.Gin.Group("/auth")
	authUserGroup.Use(middleware.LoginCheckMiddleware())
	{
		r.Gin.GET("/", func(ctx *gin.Context) { sampleHandler.GetAll(ctx) })
	}
}
```

## 最後に
間違っている、気になる等ありましたら、優しく諭していただけるととても喜びます。