1

Hopefully this is not a too stupid question, but I am having trouble with aiohttp cookie processing.

Aiohttp's CookieJar class mentions it implements cookie storage adhering to RFC 6265, which states that:

  • cookies for a given host are shared across all the ports on that host
  • Cookies do not provide isolation by port. If a cookie is readable by a service running on one port, the cookie is also readable by a service running on another port of the same server.

But if I create two aiohttp servers, one that makes you "login" and gives you a cookie back, and another one with an endpoint that expects you to have a cookie, both hosted on localhost (two different ports I guess), the cookie will not be processed.

Here's a set of 4 tests using aiohttp, pytest, pytest and pytest-aiohttp to explain:

import functools

import pytest
from aiohttp import web


pytestmark = pytest.mark.asyncio


def attach_session(f):
    @functools.wraps(f)
    async def wrapper(request: web.Request):
        session_id = request.cookies.get("testcookie")
        request["mysession"] = session_id

        response = await f(request)
        response.set_cookie("testcookie", session_id)
        return response

    return wrapper


def is_logged_in(f):
    @functools.wraps(f)
    @attach_session
    async def wrapper(request: web.Request):
        session = request["mysession"]
        if not session:
            raise web.HTTPUnauthorized
        return await f(request)

    return wrapper


async def login(_: web.Request):
    response = web.Response()
    response.set_cookie("testcookie", "somerandomstring")
    return response


@is_logged_in
async def some_endpoint(request: web.Request):
    return web.Response(text="sweet")


@pytest.fixture
def auth_client(event_loop, aiohttp_client):
    app = web.Application()
    app.router.add_post("/login", login)
    return event_loop.run_until_complete(aiohttp_client(app))


@pytest.fixture
def core_client(event_loop, aiohttp_client):
    app = web.Application()
    app.router.add_get("/some_endpoint", some_endpoint)
    return event_loop.run_until_complete(aiohttp_client(app))


async def test_login(auth_client):
    resp = await auth_client.post("/login")
    assert resp.status == 200
    assert resp.cookies.get("testcookie").value == "somerandomstring"


async def test_some_endpoint_anonymous(core_client):
    resp = await core_client.get("/some_endpoint")
    assert resp.status == 401


async def test_some_endpoint_as_logged_in(auth_client, core_client):
    resp1 = await auth_client.post("/login")
    resp2 = await core_client.get("/some_endpoint", cookies=resp1.cookies)
    assert resp2.status == 401


async def test_some_endpoint_as_logged_in_again(auth_client, core_client):
    resp1 = await auth_client.post("/login")

    _cookie = list(resp1.cookies.values())[0]
    resp2 = await core_client.get(
        "/some_endpoint", cookies={_cookie.key: _cookie.value}
    )
    assert resp2.status == 200

But from my understanding, the "test_some_endpoint_as_logged_in" test should work. Why is it returning 401, while the same thing but with sending the cookie as a dict returns 200?

  • Have you tested this with a browser connection, I think when you set up the core client and auth client, they behave like separate clients, imagine connecting to the same server with firefox and then with chrome and expecting them to share the cookies. They won't. – MichalMazurek Oct 31 '22 at 20:25
  • I don't see how a browser is relevant here. This is not about browsers at all – Clément de Nailly Nov 02 '22 at 09:44

1 Answers1

0

I think the correct way of sharing the cookies between clients would be loading the SimpleCookie object of the resp1 to the core_client.session.cookie_jar.

Changing the code of the test_some_endpoint_as_logged_in to should fix it:

async def test_some_endpoint_as_logged_in(auth_client, core_client):
    resp1 = await auth_client.post("/login")
    core_client.session.cookie_jar.update_cookies(resp1.cookies)
    resp2 = await core_client.get("/some_endpoint")
    assert resp2.status == 401

Cookie data is kept in the session object as the auth_client and core_client are different sessions with there own data cookie data is not shared. It is comparable to using a different browser with each there own cookie_jar.

Tom Dierckx
  • 666
  • 6
  • 12
  • Thanks that helps a lot. So do you think sending the cookie as a dict does not give out the origin the cookie and the server accepts it because it has no idea it's coming from a different session? – Clément de Nailly Nov 07 '22 at 09:24
  • What you are creating in `test_some_endpoint_as_logged_in_again` is a manually set cookie based on the cookies set on response resp1. So you are not actually sharing cookies you are manually setting a cookie on the request. Cookies should be shared over the entire session not just set on a single request. I think that the original test_some_endpoint_as_logged_in is not working is that the cookies are not actually on the requests. I think the expected object for cookies is wrong on the returning object from resp1.cookies. Probably that the source of aiohttp typedefs.py could give some insight. – Tom Dierckx Nov 09 '22 at 10:24