Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

The "Missing CSRF token" error #720

Open
qellyka opened this issue Dec 25, 2024 · 15 comments
Open

The "Missing CSRF token" error #720

qellyka opened this issue Dec 25, 2024 · 15 comments
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@qellyka
Copy link

qellyka commented Dec 25, 2024

I did the authorization according to the guide using the JWT token in cookies, everything works well until I check if the user has rights, here is my code:

from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column
from sqlalchemy import select

from authx import AuthX, AuthXConfig

from fastapi import FastAPI, Depends, HTTPException, Response

from pydantic import BaseModel, Field

from typing import Annotated

app = FastAPI()

config = AuthXConfig()
config.JWT_SECRET_KEY = "<--Bef,eT$qme~^yS|gH(c4{IbU$/?AwD~[F5"
config.JWT_ACCESS_COOKIE_NAME = "access_cookie"
config.JWT_TOKEN_LOCATION = ["cookies"]

security = AuthX(config=config)

engine = create_async_engine('sqlite+aiosqlite:///books.db')

new_session = async_sessionmaker(engine, expire_on_commit=False)

async def get_session():
    async with new_session() as session:
        yield session

SessionDep = Annotated[AsyncSession, Depends(get_session)]

class Base(DeclarativeBase):
    pass

class BookModel(Base):
    __tablename__ = "books"

    id: Mapped[int] = mapped_column(primary_key=True)
    title: Mapped[str]
    author: Mapped[str]

class BookPostSchema(BaseModel):
    title: str = Field(max_length= 30)
    author: str = Field(max_length= 20)

class BookSchema(BookPostSchema):
    id: int

class LoginUserSchema(BaseModel):
    login: str
    password: str = Field(min_length=8)

@app.post("/setup_db", summary="Creates a new database", tags=["The database"], dependencies=[Depends(security.access_token_required)])
async def setup_db():
    async with engine.begin() as  conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    return {"ok": True}


@app.post("/books", summary="Adds a new book to the database", tags=["Books"], dependencies=[Depends(security.access_token_required)])
async def add_book(data: BookPostSchema, session: SessionDep):
    new_book = BookModel(
        title = data.title,
        author = data.author,
    )
    session.add(new_book)
    await session.commit()

@app.get("/books", summary="Outputs all books that are in the database", tags=["Books"])
async def get_books(session: SessionDep) -> list[BookSchema]:
    query = select(BookModel)
    result = await session.execute(query)
    return result.scalars().all()

@app.post("/login", summary="Authorizes the user", tags=["User"])
def login(creds: LoginUserSchema, response: Response):
    if creds.login == "admin" and creds.password == "admin1234":
        token = security.create_access_token(uid="3422342")
        response.set_cookie(config.JWT_ACCESS_COOKIE_NAME, token)
        return {"access_token": token}
    raise HTTPException(status_code=401, detail="Incorrect login or password")

Please help, I don't know how to solve this problem.

@qellyka
Copy link
Author

qellyka commented Dec 25, 2024

Hi there,

We use GitHub issues as a place to track bugs and other development-related issues.

Please see the link below to our dedicated support line:

Help Center : Click Here

Ticket ID: WT240

Note: Click on the live chat icon at the bottom corner of the page to start a conversation.

Not found

Repository owner deleted a comment Dec 25, 2024
@yezz123 yezz123 added bug Something isn't working help wanted Extra attention is needed labels Dec 25, 2024
@yezz123
Copy link
Owner

yezz123 commented Dec 25, 2024

Hello @qellyka can you please post what the issue is happening aside here? logs or something

@razllivan
Copy link

razllivan commented Dec 26, 2024

If you’re encountering a “Missing CSRF token” error on routes protected with the access_token_required dependency that use the POST method, it’s likely that CSRF protection is required by default for certain methods. By default, JWT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']. To disable CSRF on these routes, you can remove the corresponding method(s) from this attribute. However, a better approach is to use the security.set_access_cookies method, which sets both the token and the needed CSRF cookie.

On the client side, remember to retrieve the CSRF token from the cookie and send it in the request header named after JWT_ACCESS_CSRF_HEADER_NAME. Below is a brief example of a login route illustrating how to set the cookies:

@app.post("/login", summary="Authorizes the user", tags=["User"])
def login(creds: LoginUserSchema, response: Response):
    if creds.login == "admin" and creds.password == "admin1234":
        token = security.create_access_token(uid="3422342")
        security.set_access_cookies(response=response, token=token)
        return {"access_token": token}

    raise HTTPException(status_code=401, detail="Incorrect login or password")

With this setup, your POST route is protected by CSRF, and you can send the token in the header on subsequent requests.

@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

Если вы столкнулись с ошибкой "Отсутствует токен CSRF" на маршрутах, защищенных с помощью зависимости, использующей этот метод, вполне вероятно, что защита от CSRF требуется по умолчанию для определенных методов. По умолчанию. Чтобы отключить CSRF на этих маршрутах, вы можете удалить соответствующие методы из этого атрибута. Тем не менее, лучшим подходом является использование метода, который устанавливает как токен, так и необходимый файл cookie CSRF.access_token_required``POST``JWT_CSRF_METHODS = ['POST', 'PUT', 'PATCH', 'DELETE']``security.set_access_cookies

На стороне клиента не забудьте получить токен CSRF из файла cookie и отправить его в заголовке запроса с именем after . Ниже приведен краткий пример маршрута, иллюстрирующего, как установить файлы cookie:JWT_ACCESS_CSRF_HEADER_NAME``login

@app.post("/login", summary="Authorizes the user", tags=["User"])
def login(creds: LoginUserSchema, response: Response):
    if creds.login == "admin" and creds.password == "admin1234":
        token = security.create_access_token(uid="3422342")
        security.set_access_cookies(response=response, token=token)
        return {"access_token": token}

    raise HTTPException(status_code=401, detail="Incorrect login or password")

При такой настройке ваш POST-маршрут защищен CSRF, и вы можете отправлять токен в заголовке при последующих запросах.

Got it, thanks a lot.

@qellyka qellyka closed this as completed Dec 27, 2024
@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

oops

@qellyka qellyka reopened this Dec 27, 2024
@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

I just wanted to clarify, if you add this part of the code, or rather replace it, then everything should work? It's just that if so, then I still get this error, even though the CSRF token has appeared.

@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

(backend-M2WzjYYo-py3.11) D:\dev\dot-hub.net\backend
uvicorn main:app --reload
INFO:     Will watch for changes in these directories: ['D:\\dev\\dot-hub.net\\backend']
INFO:     Uvicorn running on http://127.0.0.1:8000 (Press CTRL+C to quit)
INFO:     Started reloader process [14200] using WatchFiles
INFO:     Started server process [10068]
INFO:     Waiting for application startup.
INFO:     Application startup complete.
INFO:     127.0.0.1:51124 - "GET / HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:51124 - "GET / HTTP/1.1" 404 Not Found
INFO:     127.0.0.1:51129 - "GET /docs HTTP/1.1" 200 OK
INFO:     127.0.0.1:51129 - "GET /openapi.json HTTP/1.1" 200 OK
INFO:     127.0.0.1:51137 - "POST /login HTTP/1.1" 200 OK
INFO:     127.0.0.1:51158 - "POST /setup_db HTTP/1.1" 500 Internal Server Error
ERROR:    Exception in ASGI application
Traceback (most recent call last):
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\uvicorn\protocols\http\httptools_impl.py", line 409, in run_asgi
    result = await app(  # type: ignore[func-returns-value]
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 60, in __call__
    return await self.app(scope, receive, send)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\fastapi\applications.py", line 1054, in __call__
    await super().__call__(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\applications.py", line 113, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\middleware\errors.py", line 187, in __call__
    raise exc
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\middleware\errors.py", line 165, in __call__
    await self.app(scope, receive, _send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\middleware\exceptions.py", line 62, in __call__
    await wrap_app_handling_exceptions(self.app, conn)(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\routing.py", line 715, in __call__
    await self.middleware_stack(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\routing.py", line 735, in app
    await route.handle(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\routing.py", line 288, in handle
    await self.app(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\routing.py", line 76, in app
    await wrap_app_handling_exceptions(app, request)(scope, receive, send)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\_exception_handler.py", line 53, in wrapped_app
    raise exc
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\_exception_handler.py", line 42, in wrapped_app
    await app(scope, receive, sender)
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\starlette\routing.py", line 73, in app
    response = await f(request)
               ^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\fastapi\routing.py", line 291, in app
    solved_result = await solve_dependencies(
                    ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\fastapi\dependencies\utils.py", line 638, in solve_dependencies
    solved = await call(**solved_result.values)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\authx\main.py", line 619, in _auth_required
    return await self._auth_required(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\authx\main.py", line 351, in _auth_required
    request_token = await method(
                    ^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\authx\main.py", line 308, in get_access_token_from_request
    return await self._get_token_from_request(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\authx\main.py", line 281, in _get_token_from_request
    return await _get_token_from_request(
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "C:\Users\wylan\AppData\Local\pypoetry\Cache\virtualenvs\backend-M2WzjYYo-py3.11\Lib\site-packages\authx\core.py", line 156, in _get_token_from_request
    raise MissingTokenError(*(str(err) for err in errors))
authx.exceptions.MissingTokenError: Missing CSRF token

console

@razllivan
Copy link

@qellyka

can you send the code you use to test the endpoint?

@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

create database

@app.post("/setup_db", summary="Creates a new database", tags=["The database"], dependencies=[Depends(security.access_token_required)])
async def setup_db():
    async with engine.begin() as  conn:
        await conn.run_sync(Base.metadata.drop_all)
        await conn.run_sync(Base.metadata.create_all)
    return {"ok": True}

add book

@app.post("/books", summary="Adds a new book to the database", tags=["Books"], dependencies=[Depends(security.access_token_required)])
async def add_book(data: BookPostSchema, session: SessionDep):
    new_book = BookModel(
        title = data.title,
        author = data.author,
    )
    session.add(new_book)
    await session.commit()

@razllivan
Copy link

create database

@app.post("/setup_db", summary="Creates a new database", tags=["The database"], dependencies=[Depends(security.access_token_required)])

async def setup_db():

    async with engine.begin() as  conn:

        await conn.run_sync(Base.metadata.drop_all)

        await conn.run_sync(Base.metadata.create_all)

    return {"ok": True}

add book

@app.post("/books", summary="Adds a new book to the database", tags=["Books"], dependencies=[Depends(security.access_token_required)])

async def add_book(data: BookPostSchema, session: SessionDep):

    new_book = BookModel(

        title = data.title,

        author = data.author,

    )

    session.add(new_book)

    await session.commit()

I meant the code you used to check if a request to a specific url works. I wanted to make sure that when you send a request you send the csrf token in the request header

@qellyka
Copy link
Author

qellyka commented Dec 27, 2024

I use the documentation built into FastApi, which is available at 127.0.0.1:8000/docs

@razllivan
Copy link

I use the documentation built into FastApi, which is available at 127.0.0.1:8000/docs

You must specify the csrf token in request header taken from the cookie.
I do not know how to do this through the swagger UI, but you can write a pytest test that will do this when sending a request.

@qellyka
Copy link
Author

qellyka commented Dec 28, 2024

Then why does everything work for him if he uses Swagger UI like me?

@razllivan
Copy link

razllivan commented Dec 28, 2024

Then why does everything work for him if he uses Swagger UI like me?

in its protected route the GET method is used.


@app.get("/protected", dependencies=[Depends(security.access_token_required)])

As I already said by default csrf protection works for methods defined in JWT_CSRF_METHODS. by default it contains ['POST', 'PUT', 'PATCH', 'DELETE'] so the GET request passes without requiring a csrf token. I think you should read more about protection from CSRF attacks.

@qellyka
Copy link
Author

qellyka commented Dec 28, 2024

Okay, got it, thanks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

3 participants