Jinja2 Template을 사용하고 있는 상황에서 "어떻게 JWT(JSON Web Token)를 Front(Client)에서 Back(Server)으로 넘겨줄 수 있을까?" 라는 고민을 하게 되었습니다.
FastAPI 공식문서를 따라서 진행을 하면 http://127.0.0.1:8000/docs 에서 "Authorize" 버튼을 클릭해 값을 직접 넣어줌으로써 JWT 인증이 정상적으로 동작하는지 확인할 수 있었습니다.
하지만 제가 원하는 건 "Front(=Jinja2 Templates)에서 버튼을 클릭했을 때 JWT 값을 같이 넘겨주는 것"을 원했습니다. JWT를 같이 넘겨주기 위해서는 Back에서 넘겨준 access_token(Response Data)을 Front에서 저장을 해놓아야 합니다.
결론은 "Back에서 넘겨준 access_token을 Front에서 어디에 저장할 것인가?" 입니다.
공식문서의 작성된 JWT 절차를 따라가다보면 Dependeny에 의해 OAuth2PasswordBearer.__call__()
를 호출하는 것을 알 수 있는데, request.header에서 key 값이 "Authorization"인 값을 가져옵니다.
class OAuth2PasswordBearer(OAuth2):
...
async def __call__(self, request: Request) -> Optional[str]:
authorization: str = request.headers.get("Authorization")
scheme, param = get_authorization_scheme_param(authorization)
if not authorization or scheme.lower() != "bearer":
if self.auto_error:
raise HTTPException(
status_code=HTTP_401_UNAUTHORIZED,
detail="Not authenticated",
headers={"WWW-Authenticate": "Bearer"},
)
else:
return None
return param
이 메서드를 보고 내가 해야할 일을 알게 되었습니다.
Back에서 넘겨준 access_token(Response Data)을 Front에서 Header에 저장하는데, key 값은 "Authorization"으로 하고, value 값은 "Bearer {access_token}" 형식으로 넣어줘야 한다.
하지만 저는 request.herader에 "Authorization" key값을 어떻게 넣어줘야 하는지 도저히 알지 못했ㅅㅂ니다... (고의오타)
결론은 JWT를 LocalStorage와 Cookie 둘 중 하나에 저장해야 한다는 것을 알게 되었습니다.(도움을 주신 진우님에게 무한의 감사를..😭) 두 개 중에서 저는 Cookie에 저장하는 것을 택했습니다.
Cookie에 저장하기로 결정한 이유는 fastapi 에서 제공하는 RedirectResponse.set_cookie()
를 이용해 쉽게 cookie 안에 JWT를 저장할 수 있었기 때문입니다. (fastapi에서 제공하는 모든 Response 관련 클래스에는 .set_cookie()
존재하는 것 같음)
...
@router.post("/login")
async def login(request: Request, form_data: OAuth2PasswordRequestForm = Depends()):
user = await authenticate_user(form_data.username, form_data.password)
if not user:
return templates.TemplateResponse(
"login.html",
{
"request": request,
"errors": ["Username 또는 Password가 틀립니다"]
}
)
access_token_expires = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES)
access_token = await create_access_token(
data={"sub": user.username},
expires_delta=access_token_expires
)
response = RedirectResponse("/", status_code=302)
response.set_cookie(key="access_token", value=access_token, httponly=True)
return response
이와 같이 Front(=Client, Jinja2 Template)로 부터 요청이 왔을 때 Back에서 access_token을 만들고, access_token을 cookie에 저장할 수 있었습니다.
cookie에 저장이 되었기 때문에 그 다음 Front(=Client, Jinja2 Template)로 부터 요청이 왔을 때, 아래의 코드로 token 값을 읽고 판별할 수 있었습니다.
...
async def get_current_user(request: Request):
try:
token: str = request.cookies.get("access_token")
if token is None:
return False
payload = jwt.decode(token, JWT_SECRET_KEY, algorithms=[ALGORITHM])
username: str = payload.get("sub")
if username is None:
return False
token_data = TokenData(username=username)
except JWTError:
return False
user = # username 변수를 통해 DB에서 가져오기
if user is None:
return False
return user
Jinja2 Templates과 JWT를 같이 사용하려다 보니 어려웠던 것 같다. Jinja2 Template를 사용하지 않는다면, request.header 안에 key=Authorization, value=Bearer {access_token}
값을 넣어주는 건 어려운 일이 아닐 것 같다..?