M
M
Mikkkch2020-11-28 16:31:10
Python
Mikkkch, 2020-11-28 16:31:10

Cookies not set when calling set cookie method?

Hello, due to the fact that there is no Starlette or FastAPI tag among the Habr tags, I tagged the question with the Python tag.

My problem is that after logging in, cookies are incorrectly set to the created object of the Response class, or rather, they are not even set at all.

My authentication manager, written in order not to produce code in views (the problematic part is highlighted)

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)

Some will find the body of the _open_session method strange. So necessary. To make it easier for you to understand this method, imagine that it simply passes positional arguments like this:
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)

In the authorize method, a banal check is performed to see if a username that exists in the database is entered, and if it is found, the two passwords are compared.

Here are the functions associated with the manager:
_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')


Representation:
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
        )


Some settings:
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
}


When setting the __call__ method, which looks like this:
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'

and when redirected to the main page, the view of which looks like this:
@app.get('/')
async def homepage(auth_key: str = Depends(auth_manager)):
    pass

method displays the following:
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,application
/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': '= aa308bd8-pga4_session c10a-4d98-9bab-5656c6e9a405 7b kPpPJ + +
! NpVfHuhW4sB3YtatMs = '})

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

Header: None
Scheme:
Param:

That is, neither in the headers nor in the cookies does it find the session set in the response.

Some complain about the amount of code in the questions, and some complain about the opposite. I tried not to cram too much and endure the maximum necessary. Please help me to solve this problem.

Answer the question

In order to leave comments, you need to log in

2 answer(s)
M
Mikkkch, 2020-11-28
@Mikkkch

The problem turned out to be that instead of localhost, the domain parameter needs a value corresponding to the domain itself. That is, if you run on the locale and your value is 127.0.0.1, then the value sent to the set_cookie method must match it.

D
Dr. Bacon, 2020-11-28
@bacon

response.set_cookie what does this method return?

Didn't find what you were looking for?

Ask your question

Ask a Question

731 491 924 answers to any question