본문 바로가기
FastAPI

[FastAPI] 쿠키 설정 및 쿠키 삭제

by 혀넝 2023. 5. 2.

FastAPI 프로젝트에서 회원가입 및 로그인을 하면 토큰을 발행하게 되어 있다. 로그인 시에 해당 토큰을 프론트에 리턴하기 전에 쿠키에 설정하고, 또한 로그아웃 시에 쿠키를 삭제하는 로직을 추가하려고 한다.

로그인 코드 및 set_cookie

async def login(
    req: UserLoginReqDto, response: Response
):
    email = req.email
    password = req.password

    user = await get_user_by_email(email)
    if not user:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="User does not exist",
        )

    hashed_password = user.password
    checked_password = await verify_password(password, hashed_password)

    if not checked_password:
        raise HTTPException(
            status_code=status.HTTP_400_BAD_REQUEST,
            detail="Invalid password",
        )

    data = await generate_token_and_return_user(user)
    await set_cookie(data, response)

    return data
  • 위 코드는 간단한 로그인 코드로, get_user_by_email 함수로 해당 이메일을 가진 유저가 있는지 확인하고 없으면 에러를 리턴한다.
  • 비밀번호 확인은 verify_password 함수에서 bcrypt로 진행하고, 확인된 user 값을 generate_token_and_return_user 함수에 넘겨 토큰을 생성한다.
  • 여기서 만들어진 토큰 값으로 쿠키 설정을 하는데 그건 set_cookie 함수에서 이루어진다.
async def set_cookie(data, response: Response):
    response.set_cookie(
        key=settings.auth_cookie, value=data["access_token"], httponly=True
    )
    response.set_cookie(
        key=settings.refresh_cookie, value=data["refresh_token"], httponly=True
    )
    return
  • 위의 설명대로 data 안에 access_tokenrefresh_token이 있는 구조이기 때문에, set_cookie 함수 안에서 쿠키에 저장할 값은 data["access_token"]과 같이 가져온다.
  • 내가 만든 set_cookie 함수 말고 response에서 제공하는 set_cookie로 쿠키를 설정할 수 있다. 해당 set_cookie에서 사용할 수 있는 인자는 다음과 같다.
    • httponly: 해당 속성을 True로 하면 브라우저에서 해당 쿠키에 접근할 수 있는 영역이 제한되어 XSS(Cross-Site Scripting) 공격을 방지할 수 있다.
    • secure: True로 설정하면 https에서만 쿠키를 전송할 수 있도록 한다.
    • domain: 쿠키를 전송할 도메인을 설정한다.

 

로그아웃 및 쿠키 삭제

쿠키를 삭제하는 것으로 로그아웃을 구현하려고 하는데, 쿠키를 사용하는 방법이 두 가지 정도 있어서 둘 다 테스트를 해봤다.

1. request.cookies.clear()

로그인 시 쿠키 값을 설정한 후에 request.cookies를 하면 쿠키 값만 찍히고, request.headers를 하면 쿠키를 포함한 모든 헤더값이 찍히는 것을 볼 수 있다. 테스트는 포스트맨으로 진행했다.

print(request.headers)

>>>
Headers({
    'authorization': 'token', 
    'user-agent': 'PostmanRuntime/7.29.2', 
    'accept': '*/*', 
    'postman-token': 'postman token', 
    'host': 'localhost:8000', 
    'accept-encoding': 'gzip, deflate, br', 
    'connection': 'keep-alive', 
    'cookie': 
            'csrftoken=csrftoken;  
            access_token=access_token
            refresh_token=refresh_token; 
            sessionid=session_id', 
    'content-length': '0'
})


print(request.cookies)

>>>
{
    'csrftoken': 'csrftoken', 
    'access_token': 'access_token',
    'refresh_token': 'refresh_token', 
    'sessionid': 'session_id'
}

쿠키 값만 삭제하면 되기 때문에 공식문서를 찾아보니 이때 사용할 수 있는 것으로 request.cookies.clear()가 있다.

async def logout(req: Request, response: Response):
    print(req.cookies)
    print("==================")
    req.cookies.clear()
    print(req.cookies)
    return

 

아래와 같이 테스트를 해 본 결과 첫 줄에서는 찍히던 쿠키 값이 clear() 이후에는 찍히지 않는다.

 

2. response.delete_cookie()

request.cookies.clear()로도 쿠키 삭제가 가능하여 로그아웃을 구현할 수 있지만 이 방법은 브라우저에 저장된 쿠키가 삭제되지 않을 수 있어 Response 객체를 사용하여 쿠키 삭제 후 삭제 응답을 보내야 한다고 한다.

async def logout(req: Request, response: Response):
    print(response.headers)
    response.delete_cookie(key"access_token")
    response.delete_cookie(key="refresh_token")
    print(response.headers)
    return

첫 번째 줄의 response.headers에서는 빈 값이 나오지만 쿠키 삭제 후의 response.headers에서는 값이 찍힌다.
response.headers는 응답 헤더의 딕셔너리를 출력한다. delete_cookie() 이후에는 새로운 응답 헤더 딕셔너리가 반환되기 때문에 쿠키 삭제 후에 값이 찍히게 되는 것이다.

따라서 최종적인 로그아웃은 delete_cookie()를 사용하여 아래와 같이 구현했다.

async def logout(response: Response):
    response.delete_cookie(key=settings.auth_cookie)
    response.delete_cookie(key=settings.refresh_cookie)

    return JSONResponse(
        content={"detail": "User logged out successfully"},
        status_code=status.HTTP_200_OK,
    )

'FastAPI' 카테고리의 다른 글

[FastAPI] Request에서 user 정보 얻기  (0) 2023.04.23
[FastAPI] Pydantic validator의 재사용  (0) 2023.04.21