728x90
반응형

Nextjs App Router 서버 사이드 요청

  • Nextjs의 App Router를 통해 서버사이드에서 요청 날리게끔 구성되어 있다.
  • 요청에 쿠키를 담아 서버쪽에서 검증 받는 흐름이지만, 쿠키가 계속 안 들어왔다.
    • credentials: "include" 인데도 들어오지 않는 것에 의문을 가졌다.
  • 결론은 서버 사이드에서의 요청일 경우 직접 쿠키를 담아줘야 한다는 것이다.

쿠키 양식

"Cookie": "accessToken=tokendata; refreshToken=refreshToken"

변경된 코드

  • 헤더에 직접 입력
  • API 라우트에서 쿠키를 뽑아와 인자로 던짐

클라이언트 사이드 요청 함수

  • react query를 이용해 요청 및 관리
  • 이 때는 쿠키를 담을 수 없다.
    • 클라이언트 사이드에서는 쿠키를 가져올 수 없음. 브라우저 범위이기 때문
export interface ReferenceListResponse {
    status: number;
    code: string;
    message: string;
    result: ReferenceListItem[];
    totalCount: number;
}

export interface ReferenceListItem {
    referenceSeq: string;
    referenceName?: string;
    referenceUrl: string;
    referenceMemo?: string;
    referenceCategory: string;
    createdAt: string;
}

export const referenceListRequest = async (props: ReferenceListRequest) => {

    const response = await jsFetch<ReferenceListResponse>(`/api/reference/list`, {
        method: 'post',
        cache: 'no-store',
        body: JSON.stringify(props),
        headers: {
            'content-type': 'application/json',

        },        
    });

    return response;

};

서버 사이드 요청 함수

export interface ReferenceListRequest {
    category: string;
    page: number;
    pageSize: number;
}


export const referenceList = async (request: ReferenceListRequest, accessToken: string, refreshToken: string) => {

    const response = await fetch(
    `${ process.env.NEXT_PUBLIC_BASE_API }/reference/list`, {
        method: 'post',
        cache: 'no-store',
        body: JSON.stringify({ category: request.category, page: request.page, pageSize: request.pageSize }),
        headers: {
            'content-type': 'application/json',
            "Cookie": `accessToken=${ accessToken }; refreshToken=${ refreshToken }`
        },
        credentials: 'include', // 쿠키를 포함해서 요청 전송
        next: { tags: [ 'search-word' ] },
    });

    return response;
};
728x90
반응형

'기록 > 사이드 프로젝트' 카테고리의 다른 글

[AUTH] 검증 방법 변경 - Http Only Cookie  (0) 2025.01.31
[OAUTH] 구글 OAuth  (0) 2025.01.02
728x90
반응형

개요

  • 로그인 시 발급되는 access token을 쿠키에 세팅
  • 미들웨어에서 쿠키의 access token 을 검증

Cookie 세팅

  • 생성된 토큰을 쿠키에 세팅한다
func Login(res http.ResponseWriter, req *http.Request) {

    /*
        로그인 로직
    */

    accessTokenCookie := http.Cookie{
        Name: "accessToken",
        Value: accessToken,
        Path: "/",
        Secure: true, // HTTPS 만
        HttpOnly: true, // 브라우저에서 쿠키 조작 불가하게 세팅
        SameSite: http.SameSiteNoneMode,
    }

    refreshTokenCookie := http.Cookie{
        Name: "refreshToken",
        Value: refreshToken,
        Path: "/",
        Secure: true, // HTTPS 만
        HttpOnly: true, // 브라우저에서 쿠키 조작 불가하게 세팅
        SameSite: http.SameSiteNoneMode,
    }

    http.SetCookie(res, &accessTokenCookie)
    http.SetCookie(res, &refreshTokenCookie)

    /*
        응답 보내기
    */
}

검증 미들웨어

  • 쿠키에서 토큰을 추출해서 컨텍스트에 담는다
  • 담은 컨텍스트의 정보를 컨트롤러에서 사용한다.
// 사용자 정의 키 타입을 사용하여 컨텍스트 충돌 방지

type contextKey string

const (
    // JWT 서명에 사용할 비밀 키 (환경 변수로 관리하는 것이 좋습니다)

    // jwtSecret = configs.GlobalConfig.JwtKey // 실제 배포 시 환경 변수로 관리

    // 컨텍스트에 사용자 정보를 저장할 키
    userContextKey = contextKey("user")
)

// 사용자 정보 구조체

type User struct {
    UserId string
    UserEmail string
    UserStatus string
}

var excludeRouteList = []string{
    "/", "/api", 
    "/user/signup", "/user/login",
}

// AuthMiddleware는 accessToken 쿠키를 추출하고 JWT를 검증하는 미들웨어입니다.
func AuthMiddleware(next http.Handler) http.Handler {

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        // 제외할 경로는 바로 다음 핸들러로 넘김
        for _, route := range excludeRouteList {
            if r.URL.Path == route {
                log.Printf("Found Match Route: %s", route)
                next.ServeHTTP(w, r)
                return
            }
        }

        // accessToken 쿠키 추출
        cookie, err := r.Cookie("accessToken")

        log.Printf("Cookie: %s", cookie)

        if err != nil {
            log.Printf("Get Cookie Error :%v", err)  

            if err == http.ErrNoCookie {
                response.Response(w, response.CommonResponseWithMessage{
                    Status: http.StatusUnauthorized,
                    Code: "AUTH001",
                    Message: "No access token provided",    
                })

                return
            }

            // 다른 쿠키 에러 처리

            response.Response(w, response.CommonResponseWithMessage{
                Status: http.StatusUnauthorized,
                Code: "AUTH002",
                Message: "Invalid cookie format",
            })

            return
        }

        accessToken := cookie.Value

        userId, userEmail, userStatus, validateErr := auth.ValidateJwtTokenFromString(accessToken)


        if validateErr != nil {
            log.Printf("ValidateErr Cookie Error :%v", validateErr)

            // 토큰 만료에 대한 응답
            if strings.Contains(validateErr.Error(), "token expired") {
                response.Response(w, response.CommonResponseWithMessage{
                    Status: http.StatusUnauthorized,
                    Code: "AUTH003",
                    Message: "Token expired",
                })

                return
            }

            // 일반적인 JWT 검증 실패 응답
            response.Response(w, response.CommonResponseWithMessage{
                Status: http.StatusUnauthorized,
                Code: "AUTH004",
                Message: "Invalid token",
            })

            return        
        }

        // 사용자 정보를 구조체로 생성

        user := User{
            UserId: userId,
            UserEmail: userEmail,
            UserStatus: userStatus,
        }

        // 사용자 정보를 컨텍스트에 추가
        ctx := context.WithValue(r.Context(), userContextKey, user)
        next.ServeHTTP(w, r.WithContext(ctx))
    })
}



// 사용자 정보를 핸들러 및 컨트롤러에서 가져오는 헬퍼 함수
func GetUserFromContext(ctx context.Context) (User, bool) {
    user, ok := ctx.Value(userContextKey).(User)
    return user, ok
}

컨트롤러에서 추출된 정보들 사용


func SampleController(res http.ResponseWriter, req *http.Request) {
    user, ok := middlewares.GetUserFromContext(req.Context())

    if !ok {    
        dto.SetErrorResponse(res, 401, "01", "JWT Verifying Error", nil)

        return
    }
    /*
        이후 로직
    */

    dto.SetResponse(res, 200, "01")
}
728x90
반응형

+ Recent posts