Issue DRF-JWT: 헤더에 자동으로 Access Token이 추가되지 않을 때

KimJiHong·2023년 11월 25일
1

Issue

목록 보기
3/7
post-thumbnail

Issue

나는 Django REST API Framework 를 통해 Login API 를 구축하고

authentication 방법을 Session 방식이 아닌

JWT 방식으로 Access Token 을 검사를 통한 사용자 인증을 구현했다. GitHub 참고

하지만, POST-MAN 을 통해서 HTTP 헤더에 직접 JWT Authorization 을 추가했을 때는 정상적으로

JWT Access Token 을 검사하지만, 브라우저에서 API 요청시에는 헤더에 Authorization 추가가 되지 않아

JWT 인증 실패로 API 가 reject 되고 있었다.

그래서, 해당 이슈를 해결하기 위해서 자동으로 헤더에 포함하는 방법을 구글링해 봤지만

대부분의 게시글이 simple JWT 예제와 POST-MAN 테스트 거나

Fetch API 로 요청 헤더에 Access Token을 직접 추가해서 API 요청을 보내는 방식을 설명하고 있었다.

내가 JWT 를 구현할 때 httponly=True 옵션으로 JavaScript에서는 토큰에 접근할 수 없게 구현해

Fetch API를 통해 헤더에 직접 추가해서 요청을 보낼 수 없을 뿐더러

내가 구현할 방향은 브라우저가 자동으로 헤더에 JWT 를 포함해서 보내고 싶었다.


Check.1 Cookies

먼저 로그인 시 발급 받은 JWT Access Token 과 Refresh Token 이 제대로 쿠키에 저장되는지 확인해 보기로 했다.

먼저 내가 구축한 Login API의 views.py 부분이다.

# account/api/views.py

@api_view(['POST'])
@permission_classes([AllowAny])
def LoginAPI(request):
    # 사용자 인증
    user = authenticate(username= request.data.get("username"), password= request.data.get("password"))
    
    # 등록된 사용자인지 확인
    if user is not None:
        serializer = UserSerializer(user)

        # JWT 토큰
        token = TokenObtainPairSerializer.get_token(user)
        refresh_token = str(token)
        access_token = str(token.access_token)

        response = Response(
            {
                "user": serializer.data,
                "message": "login success",
                "token": {
                    "access": access_token,
                    "refresh": refresh_token,
                },
            },
            status=status.HTTP_200_OK,
        )

        # JWT 토큰을 브라우저의 쿠키내 저장
        response.set_cookie("access_token", access_token, httponly=True)
        response.set_cookie("refresh_token", refresh_token, httponly=True)

        # 최근 로그인 기록 업데이트
        update_last_login(None, user)

        return response
    
    else:
        return Response(status=status.HTTP_400_BAD_REQUEST)

그리고 간단한 Login 템플릿을 만들고 쿠키 저장소를 확인 해봤다.

쿠키 저장소에는 정상적으로 Access Token과 Refresh Token을 받고 있지만,

Request 헤더에만 제대로 포함되지 않고 있는 것 같다.


Check.2 Request

쿠키 저장소에 정상적으로 포함된 것을 확인해서

이제 브라우저가 request 전송 시 Access Token 을 포함해서 보내는지 확인 해봤다.

브라우저가 자동으로 Authorization 헤더에 Access Token 을 포함하는게 아니라

Access Token 을 Cookie 에 포함해서 request 를 보내고 있어

DRF 에서 Access Token 을 가져오지 못해 JWT 인증을 정상적으로 하질 못하고 있었던 것이다.


Solution - Custom Middleware

나는 JavaScript 를 사용하지 않고 어떻게 Cookie에 있는 Access Token 을

Authorizaion 으로 옮겨 request를 보낼까 고민하며

다시 Access Token 헤더 관련해서 검색하다가

HTTP request를 view로 이동 시켜주는 middleware 부분에서

Cookie에 저장된 Access Token 을 Authorization 으로 옮겨 뷰로 옮겨주면 된다는 게시글을 발견했다.

따라서, 쿠키에 저장된 JWT 정보를 Authorization 로 옮기는 커스텀 미들웨어를 작성했다.

#middleware/jwt_authentication_middleware.py

from rest_framework_simplejwt.tokens import AccessToken, RefreshToken

class JWTAuthenticationMiddleware:
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        # 쿠키에서 JWT 토큰 데이터를 읽기
        access_token_value = request.COOKIES.get('access_token')

        if access_token_value:
            try:
                # JWT 토큰을 디코딩하여 사용자 데이터를 가져옴
                access_token = AccessToken(access_token_value)
                user = access_token.payload.get('user_id')

                # HTTP Authorization 헤더에 JWT ACCESS 헤더 추가
                request.META['HTTP_AUTHORIZATION'] = f'Bearer {access_token_value}'

            except Exception as e:
                # JWT 토큰 디코딩 실패
                pass

        response = self.get_response(request)

        return response
# main/settings.py

# ...

MIDDLEWARE = [
    'middleware.jwt_authentication_middleware.JWTAuthenticationMiddleware', # JWT 를 헤더에 포함
    
    # ... 다른 미들웨어 설정
]

미들웨어를 통해, request 헤더를 변경 후 runserver 로 API 확인 결과

정상적으로 JWT 기반으로 사용자 인증을 진행하고 API 권한도 제대로 작동하게 되었다!


Ref.


https://blog.naver.com/netscout82/222008511406
정말 감사합니다...

profile
https://h0ng.dev

1개의 댓글

comment-user-thumbnail
2024년 10월 29일

잘 봤습니다. 덕분에 고민하던 것을 해결 할 수 있었습니다.

다만, cookie 를 활용한 jwt 관리를 할 경우 브라우저 외에 모바일 어플이나 일반 응용프로그램에서는 적용이 어렵다는 생각이 드는데 혹시 해결이 하신 적 있나요?

답글 달기