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_token과 refresh_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 |