The "Missing CSRF token" error
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.
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:
Ticket ID: WT240
Note: Click on the live chat icon at the bottom corner of the page to start a conversation.
Not found
Hello @qellyka can you please post what the issue is happening aside here? logs or something
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.
Если вы столкнулись с ошибкой "Отсутствует токен 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.
oops
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.
(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
@qellyka
can you send the code you use to test the endpoint?
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()
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
I use the documentation built into FastApi, which is available at 127.0.0.1:8000/docs
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.
Then why does everything work for him if he uses Swagger UI like me?
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.
Okay, got it, thanks.