[FastAPI] Jinja2 Template을 사용할 때 어디에 어떻게 JWT를 저장할까?

최더디·2022년 4월 7일
0
post-thumbnail

📍 문제 상황

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} 값을 넣어주는 건 어려운 일이 아닐 것 같다..?

profile
focus on why

0개의 댓글