@Mikkkch

Кукисы не устанавливаются при вызове метода set cookie?

Здравствуйте, по причине того, что среди тэгов Хабра не присутствует тэга Starlette или FastAPI, я пометил вопрос тэгом Python.

Моя проблема заключается в том, что после входа в систему, созданному объекту класса Response некорректно устанавливаются cookie, вернее даже они не устанавливаются вовсе.

Мой менеджер аутентификации, написанный для того, чтобы не плодить код в представлениях (выделена проблемная часть)
class Authentication:

    def __init__(
        self,
        *,
        cookie_key: str,
        cookie_domain: str,
        cookie_httponly: bool,
        cookie_max_age: int,
        crud: ConsumersCRUD,
        credentials_exception_class: Type[Exception],
        secret: str,
        algorithm: str
    ) -> None:
        self.cookie_key = cookie_key
        self.cookie_domain = cookie_domain
        self.cookie_httponly = cookie_httponly
        self.cookie_max_age = cookie_max_age
        self.crud = crud
        self.credentials_exception_class = credentials_exception_class
        self.secret = secret
        self.algorithm = algorithm

    async def _open_session(self, response: Response, username: str) -> None:
        cookie_data = self.__dict__.copy()
        pattern = 'cookie_'

        for parameter in cookie_data.copy():
            if not parameter.startswith(pattern):
                cookie_data.pop(parameter)
            else:
                parameter_without_prefix = parameter.removeprefix(pattern)
                cookie_data[parameter_without_prefix] = cookie_data.pop(parameter)

        cookie_data['value'] = _generate_token(
            username, self.cookie_max_age, self.secret, self.algorithm
        )

        return response.set_cookie(**cookie_data)

    async def authorize(self, response: Response, credentials: OAuth2PasswordRequestForm) -> None:
        consumer = await self.crud.get('username', credentials.username)

        password_match = False

        if consumer:
            password_match = _check_password(
                credentials.password, consumer.get('password')
            )

        if not password_match:
            raise self.credentials_exception_class('Incorrectly entered login or password.')

        return await self._open_session(response, credentials.username)

Некоторым покажется странным тело метода _open_session. Так необходимо. Для того, чтобы Вам было проще воспринимать этот метод, представьте, что в нем просто передаются позиционные аргументы вот таким образом:
async def _open_session(self, response: Response, username: str) -> None:
    token = _generate_token(username, self.cookie_max_age, self.secret, self.algorithm)
    response.set_cookie(key=self.cookie_key, domain=self.cookie_domain,
                        value=token, max_age=self.cookie_max_age, httponly=self.cookie_httponly)

В методе authorize осуществляется банальная проверка на то, введено ли существующее в базе имя пользователя и в случае, если оно найдено, осуществляется сопоставление двух паролей.

Вот сопутствующие менеджеру функции:
_context = CryptContext(schemes=["bcrypt"], deprecated="auto")


def _check_password(password: str, encrypted_password: str) -> bool:
    return _context.verify(password, encrypted_password)


def _generate_token(username: str, expires: int, secret: str, algorithm: str) -> str:
    payload = {
        'sub': username,
        'exp': datetime.utcnow() + timedelta(seconds=expires)
    }
    return jwt.encode(payload, secret, algorithm).decode('utf-8')


Представления:
auth = fastapi.APIRouter()
auth_templates = Jinja2Templates(directory=settings.CONSUMERS_FRONTEND)
consumers = ConsumersCRUD(database, consumers)

auth_manager = Authentication(
    **settings.AUTH_MANAGER_SETTINGS,
    crud=consumers,
    credentials_exception_class=CredentialsValidationException
)

async def _convenient_display_template(template: str, request: fastapi.Request, **context):
    # The request parameter might be required only in the context,
    # but since it is required for display, it is handed down separately.
    context['request'] = request
    return auth_templates.TemplateResponse(template, context)


@auth.get('/sign-in')
async def sign_in(request: fastapi.Request):
    return await _convenient_display_template('sign-in.html', request=request)


@auth.post('/sign-in')
async def sign_in(
    request: fastapi.Request,
    credentials: OAuth2PasswordRequestForm = fastapi.Depends(OAuth2PasswordRequestForm)
):
    try:
        response = RedirectResponse(settings.REDIRECT_AFTER_SIGN_IN, status_code=303)
        await auth_manager.authorize(response, credentials)
        return response

    except CredentialsValidationException as error_details:
        return await _convenient_display_template(
            'sign-in.html', request, error=error_details
        )


Некоторые настройки:
REDIRECT_AFTER_SIGN_UP = "/sign-in"
REDIRECT_AFTER_SIGN_IN = "/"

COOKIE_KEY = 'Authorization'
COOKIE_DOMAIN = 'localhost'
COOKIE_HTTPONLY = True
ACCESS_TOKEN_EXPIRES = 1800

SECRET = 'sdksjkajkjaJKDJKjkdjks3984984mjkdjksjdk'
ALGORITHM = 'HS256'

AUTH_MANAGER_SETTINGS = {
    'cookie_key': COOKIE_KEY,
    'cookie_domain': COOKIE_DOMAIN,
    'cookie_httponly': COOKIE_HTTPONLY,
    'cookie_max_age': ACCESS_TOKEN_EXPIRES,
    'secret': SECRET,
    'algorithm': ALGORITHM
}


При установке метода __call__, который имеет следующий вид:
async def __call__(self, request: Request) -> str:
        """
        """
        print(request.headers)
        print(request.cookies)
        cookie_header = request.cookies.get(self.cookie_key)
        cookie_scheme, cookie_param = get_authorization_scheme_param(cookie_header)

        print('Header:', cookie_header)
        print('Scheme:', cookie_scheme)
        print('Param:', cookie_param)
        return 'dssd'

и при перенаправлении на главную страницу, представление которой выглядит так:
@app.get('/')
async def homepage(auth_key: str = Depends(auth_manager)):
    pass

метод отображает следующее:
Headers({'host': '127.0.0.1:8000', 'connection': 'keep-alive', 'cache-control': 'max-age=0', 'upgrade-insecure-requests': '1', 'user-agent': '', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,applic
ation/signed-exchange;v=b3;q=0.9', 'sec-fetch-site': 'same-origin', 'sec-fetch-mode': 'navigate', 'sec-fetch-user': '?1', 'sec-fetch-dest': 'document', 'referer': '127.0.0.1:8000/c
onsumers/sign-in', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7', 'cookie': 'pga4_session=aa308bd8-c10a-4d98-9bab-5656c6e9a405!7b+kPpPJ+
NpVfHuhW4sB3YtatMs='})

{'pga4_session': 'aa308bd8-c10a-4d98-9bab-5656c6e9a405!7b+kPpPJ+NpVfHuhW4sB3YtatMs='}

Header: None
Scheme:
Param:

То есть ни в хедерах, ни в кукисах не находит ту установленную в ответе сессию.

Некоторые жалуются на большой объем кода в вопросах, а некоторые жалуются на обратное. Я постарался не впихивать лишнего и вынести максимум необходимого. Помогите, пожалуйста, в решении этой проблемы.
  • Вопрос задан
  • 1956 просмотров
Решения вопроса 1
@Mikkkch Автор вопроса
Проблема оказалась в том, что вместо localhost в параметре domain необходимо значение, соответствующее самому домену. То есть если вы запускаете на локале и значение у вас равняется 127.0.0.1, то и значение, отправляемое в метод set_cookie должно ему соответствовать.
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы