IndominusByte / fastapi-jwt-auth

FastAPI extension that provides JWT Auth support (secure, easy to use, and lightweight)
http://indominusbyte.github.io/fastapi-jwt-auth/
MIT License
627 stars 143 forks source link

Fetching CSRF from headers requires update #94

Open shivam221098 opened 1 year ago

shivam221098 commented 1 year ago

In this line, https://github.com/IndominusByte/fastapi-jwt-auth/blob/a6c06193319da0e4976c7472966f3a2891e0d50c/fastapi_jwt_auth/auth_jwt.py#L549 the code is trying to get CSRF_TOKEN from the headers and treating response.headers as a dict object which is true. But the headers don't contain the default "X-CSRF-Token". It contains the key-value pair with cookies as key and all cookie info as a string separated by ;.

So whenever someone tries to get CSRF token the code is unable to find the key "X-CSRF-Token" in the headers but now it's inside key cookies, which needs to be parsed for extracting CSRF_TOKEN.

That's why whenever someone uses csrf_protect as True, they get a Missing CSRF Token error every time

I can see the code is not updated for the last 2 years. That might be the reason that it is not in compliance with the browser's headers.

ramazanix commented 1 year ago

My friend, @shivam221098, its Double Submit Cookie Pattern. You need to repeat your csrf_access_token from Authorize.cookies in request' header by X-CSRF-Token.

Example:

cookies = response.cookie headers = {'X-CSRF-Token': cookies.get('csrf_access_token')} response = await client.post('your_path', headers=headers)

shivam221098 commented 1 year ago

Thanks for the info @Ramazan2002. Is that csrf verification works for you?

If yes, Can you share the sample function which you have implemented? That would be a help 🙏

ramazanix commented 1 year ago

@shivam221098 For example I use it in my tests.

correct_cookies: list[str] = [
    "access_token_cookie",
    "csrf_access_token",
    "csrf_refresh_token",
    "refresh_token_cookie",
]

user_data = {"username": "Sam", "password": "sam_password"}

@pytest.mark.asyncio
async def test_logout(client: AsyncClient):
    """
    Trying to log out
    """

    # creating_user
    response = await client.post("/users/", json=user_data)
    assert response.status_code == 201
    assert exact_schema(user) == response.json()
    assert response.json().get("username") == user_data["username"]

    # authorization
    response = await client.post("/auth/login/", json=user_data)
    assert response.status_code == 200
    assert exact_schema(success) == response.json()
    assert list(response.cookies.keys()) == correct_cookies  # Check that correct_cookies set in your browser cookie

    cookies = response.cookies

    response = await client.delete("/auth/logout/")
    assert response.status_code == 401
    assert exact_schema(error) == response.json()
    assert response.json().get("detail") == "Missing CSRF Token"  # Checking that I get error when send request without X-CSRF-Token in headers

    headers = {"X-CSRF-Token": cookies.get("csrf_access_token")}
    response = await client.delete("/auth/logout/", headers=headers)
    assert response.status_code == 200
    assert exact_schema(success) == response.json()
    assert len(response.cookies) == 0
ramazanix commented 1 year ago

@shivam221098 Please, close issue if it helped you