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?

More than 1 year has passed since last update.

【Go】ログイン機能でウェブアプリを作ってみる(11)

Posted at

こんにちは。

Part 11は「ユーザー情報の取得 /restricted/user/me」です。

今回の目標

  • ユーザー情報の取得処理を実装する!
  • auth_middlewareの実装

前回まででログインはできるようになりました。
今回は本当にログインできているのか?確認するためのエンドポイントを作成します。

全体の流れの確認

  • 仮登録 /auth/register/initial
    • クライアントからemail, passwordを受け取る
    • email宛に本人確認トークンを送信する
  • 本登録 /auth/register/complete
    • クライアントからemailと本人確認トークンを受け取る
    • ユーザーの本登録を行う
  • ログイン /auth/login
    • クライアントからemail, passwordを受け取る
    • 認証トークンとしてJWTを返す
  • ユーザー情報の取得 /restricted/user/me
    • クライアントからJWTを受け取る
    • ユーザー情報を返す

必要な機能を考えよう!

箇条書きしてみます。

  • クライアントからJWTを受け取る必要がある
  • IDからユーザー情報を取得する

ざっと書き出すとこんな感じです。

ただ、このまま1つのファイルに書き出すとどこに何があるかわからなくなるので役割でまとめてみます。

パッケージ 役割 機能
Middleware 事前・事後処理 ・リクエストからJWTを受け取り、ユーザー情報をcontextに埋め込む
Repository DBとのやりとり ・UserIDを使ってDBからユーザー情報の取得
Usecase ログイン処理を行う ・Repositoryからユーザー情報を取得
Handler リクエストボディの取得レスポンスの作成 ・レスポンスの作成

こんな感じで実装していきます。
ではやっていきましょう!

Middleware

Middleware?

  • 「リクエストのJWTを受け取り、ユーザー情報をcontextに埋め込む」はほぼ全ての処理で共通
    • 例えばユーザー情報を更新する処理、削除する処理など。ログイン後に行う処理では必須
  • そういった共通の処理はMiddlewareとしてまとめて使いまわせるようにする

というわけで認証用のミドルウェアの実装をやっていきましょう!

middleware/auth_middleware.go

package middleware

import (
	"login-go/auth"

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

func AuthMiddleware(jwter auth.IJwtParser) func(next echo.HandlerFunc) echo.HandlerFunc {
	return func(next echo.HandlerFunc) echo.HandlerFunc {
		return func(c echo.Context) error {
			if err := jwter.SetAuthToContext(c); err != nil {
				return err
			}
			
			return next(c)
		}
	}
}

middlewareの解説

func MyMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		// 本来の処理の前に行いたい処理
		doSomethingBefore()

		defer func(){
			// 本来の処理の後に行いたい処理
			doSomethingAfter()
		}()

		// やりたい処理
		return next(c)
	}
}
  • next(c)が本来やりたい処理
    • handlerのLogin(c echo.Context) errorやActivate(c echo.Context) errorがここで行われる
  • 例えばその前後になんか処理を入れたい、と思ったらこんな感じになる

Repository

repository/user_repository.go

type IUserRepository interface {
	PreRegister(ctx context.Context, u *entity.User) error
	GetByEmail(ctx context.Context, email string) (*entity.User, error)
	Delete(ctx context.Context, id entity.UserID) error
	Activate(ctx context.Context, u *entity.User) error
+	Get(ctx context.Context, uid entity.UserID) (*entity.User, error)
}

repository/user_repository.go

func (r *userRepository) Get(ctx context.Context, uid entity.UserID) (*entity.User, error) {
	query := `SELECT 
		id, email, password, salt, state, activate_token, updated_at, created_at
		FROM user WHERE id = ?`
	u := &entity.User{}
	if err := r.db.GetContext(ctx, u, query, uid); err != nil {
		return nil, fmt.Errorf("failed to get: %w", err)
	}
	return u, nil
}

Usecase

usecase/user_usecase.go

type IUserUsecase interface {
	PreRegister(ctx context.Context, email, pw string) (*entity.User, error)
	Activate(ctx context.Context, email, token string) error
	Login(ctx context.Context, email, password string) ([]byte, error)
+	Get(ctx context.Context, uid entity.UserID) (*entity.User, error)
}
func (uu *userUsecase) Get(ctx context.Context, uid entity.UserID) (*entity.User, error) {
	u, err := uu.ur.Get(ctx, uid)
	if err != nil {
		return nil, err
	}
	return u, nil
}

Handler

handler/user_handler.go

type IUserHandler interface {
	PreRegister(c echo.Context) error
	Activate(c echo.Context) error
	Login(c echo.Context) error
+	GetMe(c echo.Context) error
}
func (h *userHandler) GetMe(c echo.Context) error {
	// echo.ContextからUserIDを取得
	uid, err := auth.GetUserIDFromEchoCtx(c)
	if err != nil {
		return err
	}

	ctx := c.Request().Context()
	// UserIDからユーザー情報を取得
	u, err := h.uu.Get(ctx, uid)
	if err != nil {
		return err
	}

	return c.JSON(http.StatusOK, echo.Map{
		"id":         u.ID,
		"email":      u.Email,
		"updated_at": u.UpdatedAt,
		"created_at": u.CreatedAt,
	})
}

Router

router.go

package main

import (
	"login-go/auth"
	"login-go/handler"
	"login-go/mail"
+	myMiddleware "login-go/middleware"
	"login-go/repository"
	"login-go/usecase"

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

func NewRouter(db *sqlx.DB, mailer mail.IMailer, jwter *auth.JwtBuilder) *echo.Echo {
	e := echo.New()

	ur := repository.NewUserRepository(db)
	uu := usecase.NewUserUsecase(ur, mailer, jwter)
	uh := handler.NewUserHandler(uu)

	a := e.Group("/api/auth")
	a.POST("/register/initial", uh.PreRegister)
	a.POST("/register/complete", uh.Activate)
	a.POST("/login", uh.Login)

+	r := e.Group("/api/restricted")
+	r.Use(myMiddleware.AuthMiddleware(jwter))
+	r.GET("/user/me", uh.GetMe)

	return e
}

確認

これで完成です!
本当にユーザー情報が取得できるか確認していきましょう!
まずはログインしてaccess_tokenを取得します。

$ curl -XPOST localhost:8000/api/auth/login \
	-H 'Content-Type: application/json; charset=UTF-8' \
	-d '{"email": "test-user-1@example.xyz", "password": "foobar"}'
{"access_token":"eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODg1MzA0NDUsImlhdCI6MTY4ODQ0NDA0NSwiaXNzIjoibG9naW4tZ28iLCJzdWIiOiJhY2Nlc3MtdG9rZW4iLCJ1c2VyX2lkIjoxMDAwMDR9.TAeic3hpLctAdsL2mXvMfwO62Dh3-lfyHxA_NFK8bhAF0vmgN4DXw66KLWLoZDjCzHcVlFsjjQpoZcnfQ-F3RJManB16MsQOMvFHJojruk_cX85eC4JDUg7Zt9ig6VP49Iak3tVbTR1Y__UW8w21-sbXm_3qmFjWLLb2QEj0zsrrvQxf8_M_tJPNlQgemTc9D4DKKG3bcd9NERvM5ABJjNzPQsxV18MxxdGdE3BksWKb6s-NBOoXBKMuG26esvy0YQf-W2t1bIUYh4TmqZJXn1vNCQBSXZ8J9FyqJCoeItQgLFro4-iN_YC06jMDzSSts2QvYR1QE4qYvxNVk7jGdg"}

このトークンをAuthorizationヘッダーに含めて/api/restricted/user/meにリクエストを投げましょう。

curl -XGET localhost:8000/api/restricted/user/me \
	-H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE2ODg1MzA0NDUsImlhdCI6MTY4ODQ0NDA0NSwiaXNzIjoibG9naW4tZ28iLCJzdWIiOiJhY2Nlc3MtdG9rZW4iLCJ1c2VyX2lkIjoxMDAwMDR9.TAeic3hpLctAdsL2mXvMfwO62Dh3-lfyHxA_NFK8bhAF0vmgN4DXw66KLWLoZDjCzHcVlFsjjQpoZcnfQ-F3RJManB16MsQOMvFHJojruk_cX85eC4JDUg7Zt9ig6VP49Iak3tVbTR1Y__UW8w21-sbXm_3qmFjWLLb2QEj0zsrrvQxf8_M_tJPNlQgemTc9D4DKKG3bcd9NERvM5ABJjNzPQsxV18MxxdGdE3BksWKb6s-NBOoXBKMuG26esvy0YQf-W2t1bIUYh4TmqZJXn1vNCQBSXZ8J9FyqJCoeItQgLFro4-iN_YC06jMDzSSts2QvYR1QE4qYvxNVk7jGdg'
{"created_at":"2023-07-03T08:56:05.656498Z","email":"test-user-1@example.xyz","id":100004,"updated_at":"2023-07-03T08:56:56.760694Z"}

はい、ちゃんとユーザー情報を取得できました!

まとめ

今回はやった作業はこんな感じ

  • ユーザー情報の取得
  • middlewareの実装

これで全作業が終わりです!

今日作成したアプリをgithubに追加しました。
必要な場合はこちらのリンクをクリックしてください。

今日は以上です。
ありがとうございました。

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?