【问题标题】:Reading Cookie from React (backend with FastAPI + fastapi-jwt-auth)从 React 读取 Cookie(使用 FastAPI + fastapi-jwt-auth 的后端)
【发布时间】:2022-04-27 14:57:32
【问题描述】:

我在使用 Axios 和 FastAPI 理解 Cookie 中的 JWT 时遇到了一些问题。

我正在尝试使用 React 为前端和 FastAPI 为后端制作一个简单的应用程序。由于这更像是我的学习项目,所以我决定使用 JWT 进行身份验证并将其存储在 Cookie 中。

身份验证流程非常基础。用户通过 POST 向后端发送凭据,后端会将 JWT 设置为 Cookie 并将其发送回。

我的问题是我无法读取返回到前端的 cookie。

现在,我了解到您无法从 Javascript 读取 HttpOnly cookie,因此即使 cookie 是从登录请求中设置的,我也无法从我的 React 应用程序中看到它们。

如果我使用 Insomnia 检查登录 API 的行为,它可以正常工作并设置适当的 cookie。

但是,我用于 JWT 身份验证的包 fastapi-jwt-auth 通过 Cookie 发送 CSRF 令牌。这不是 httpOnly。

所以为了获取和使用我的 CSRF 令牌,我需要从 React 读取 Cookie。我认为我可以这样做,因为嘿,CSRF 令牌不是 HttpOnly。但是从图片中可以看出,标题中不存在set-cookie

我假设这是因为从后端发送的 cookie 是与 HttpOnly cookie 而不是 HttpOnly cookie 的混合。但我无法从我的研究中证实这一点。

那么我怎样才能从响应中获取这个非 HttpOnly cookie?或者这根本不可能?

这是我的示例项目。

前端

package.json

{
  "name": "frontend",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "@testing-library/jest-dom": "^5.11.9",
    "@testing-library/react": "^11.2.5",
    "@testing-library/user-event": "^12.8.0",
    "@types/jest": "^26.0.20",
    "@types/node": "^12.20.4",
    "@types/react": "^17.0.2",
    "@types/react-dom": "^17.0.1",
    "axios": "^0.21.1",
    "react": "^17.0.1",
    "react-dom": "^17.0.1",
    "react-scripts": "4.0.3",
    "typescript": "^4.2.2",
    "web-vitals": "^1.1.0"
  },
  "scripts": {
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test",
    "eject": "react-scripts eject"
  },
  "eslintConfig": {
    "extends": [
      "react-app",
      "react-app/jest"
    ]
  },
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  }
}

src/App.tsx

import React, { useState } from "react";
import "./App.css";
import { authAPI } from "networking/api";

const App = () => {
    const [email, setEmail] = useState("");
    const [password, setPassword] = useState("");

    const handleLogin = async () => {
        try {
            const response = await authAPI.login({ email, password });
            console.log(response);
            const cookies = response.headers["set-cookie"];
            console.log(cookies);
        } catch (err) {
            console.error(err);
        }
    };

    return (
        <div className="App">
            <input
                value={email}
                onChange={(event) => setEmail(event.target.value)}
                type="email"
            />
            <input
                value={password}
                onChange={(event) => setPassword(event.target.value)}
                type="password"
            />
            <button type="submit" onClick={handleLogin}>
                login
            </button>
        </div>
    );
};

export default App;

src/networking/api.ts

import axios from "axios";
import { Auth } from "models/auth";

const client = axios.create({
    baseURL: "http://localhost:8000/",
    responseType: "json",
    headers: {
        "Content-Type": "application/json",
    },
    withCredentials: true,
});

const auth = () => {
    return {
        async login(credential: Auth) {
            return await client.post("auth/login", credential);
        },
    };
};

const authAPI = auth();

export { authAPI };
export default client;

src/models/auth.ts

type Auth = {
    email: string;
    password: string;
};

export type { Auth };

后端

Pipfile

[[source]]
url = "https://pypi.org/simple"
verify_ssl = true
name = "pypi"

[packages]
fastapi = "*"
fastapi-jwt-auth = "*"
uvicorn = "*"

[dev-packages]

[requires]
python_version = "3.9"

main.py

import uuid

from fastapi import FastAPI, HTTPException, status, Body, Depends
from fastapi_jwt_auth import AuthJWT
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel, BaseSettings
import uvicorn


app = FastAPI()


SECRET_KEY = '6qvsE3BBe7xvG4azL8Wwd3t_uVqXfFot6QRHIHZREkwrTZYnQHv6fSjInCB7'


class Settings(BaseSettings):
    authjwt_secret_key: str = SECRET_KEY
    authjwt_token_location: set = {'cookies'}
    authjwt_cookie_secure: bool = True
    authjwt_cookie_csrf_protect: bool = True
    authjwt_cookie_samesite: str = 'lax'


@AuthJWT.load_config
def get_config():
    return Settings()


CORS_ORIGINS = ["http://localhost:8080", "http://localhost:8000", "http://localhost:5000", "http://localhost:3000",]
app.add_middleware(
    CORSMiddleware,
    allow_origins=CORS_ORIGINS,
    allow_credentials=True,
    allow_methods=['*'],
    allow_headers=['*'],
)


class UserLoginIn(BaseModel):
    email: str
    password: str


@app.post('/auth/login')
async def login_user(user_in: UserLoginIn = Body(...), Authorize: AuthJWT = Depends()):
    try:
        user_id = str(uuid.uuid4())
        # create tokens
        access_token = Authorize.create_access_token(subject=user_id)
        refresh_token = Authorize.create_refresh_token(subject=user_id)
        # set cookies
        Authorize.set_access_cookies(access_token)
        Authorize.set_refresh_cookies(refresh_token)
    except Exception as err:
        raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail='Failed Authorization.')
    return {'message': 'Successfully login'}


if __name__ == '__main__':
    uvicorn.run(app, host='0.0.0.0', port=8000)

提前感谢您的任何见解:)

【问题讨论】:

  • 如果您想测试您的代码,您可以禁用 HttpOnly 标志并读取 cookie 值..但这应该是您的本地测试,仅出于安全角度考虑。
  • @MukulSharma 好吧,在这种情况下,我确信我可以读取 Cookie,但是我无法在生产中使用它
  • @dropscar 你最终解决了这个问题吗?介意将解决方案发布为其他人用作帮助的答案吗?谢谢
  • @uberrebu 你好!不,我最终通过登录成功响应传递了 CSRF 令牌。因此,如果用户登录 API,则将 CSRF 令牌返回给客户端。然后将其设置为 axios 的标题。希望有帮助!

标签: reactjs cookies axios jwt fastapi


【解决方案1】:

这似乎是由 Chrome 80 最近更新使用 SameSite 策略的 cookie 引起的问题 - 可以在此处阅读有关此策略的更多信息:https://www.troyhunt.com/promiscuous-cookies-and-their-impending-death-via-the-samesite-policy/

我已经重新创建了您的环境,看起来我们可以通过设置轻松让 cookie 显示在 Chrome 的 Cookie 存储中

    authjwt_cookie_samesite: str = 'none'

然后将默认为 DevTools Network 标头中的“Lax”,这是 Chrome 的默认策略(请参阅我附加的帖子)。当我设置

    authjwt_cookie_samesite: str = 'lax'

并检查了 Chrome 的 DevTools 网络标头,看起来有一个错误警告(b/c Chrome 接受这些参数大写)。 image

这似乎是图书馆未解决的问题:https://github.com/IndominusByte/fastapi-jwt-auth/issues/79

【讨论】:

    【解决方案2】:

    对于会话,您应该使用 HttpOnly,因为它每次在 JWT 上的算法几乎相同,CSRF cookie 可以被 JS 读取,因为它每次都会更改,并且您需要 csrf-token 来发送请求。

    How to read a HttpOnly cookie using JavaScript

    【讨论】:

      猜你喜欢
      • 2022-12-25
      • 2022-10-16
      • 2021-05-02
      • 2021-02-01
      • 1970-01-01
      • 1970-01-01
      • 2023-02-10
      • 1970-01-01
      • 2021-02-06
      相关资源
      最近更新 更多