Addons#

uapi ships with several useful addons.

Redis Async Sessions#

Tip

This addon handles server-side sessions, which are anonymous by default. If you’re looking for login functionality, see uapi.login which builds on top of sessions.

The uapi.sessions.redis.configure_async_sessions() addon enables the use of cookie sessions using Redis as the session store. This addon requires the use of an aioredis 1.3 connection pool.

First, configure the addon by giving it your app instance and optionally overridding parameters:

from aioredis import create_pool
from uapi.sessions.redis import configure_async_sessions

session_store = configure_async_sessions(app, await create_pool(...))

Once configured, handlers may declare a parameter of type uapi.sessions.redis.AsyncSession. The session object is a dict[str, str] subclass, and it needs to have the uapi.sessions.redis.AsyncSession.update_session() coroutine awaited to persist the session.

async def my_session_handler(session: AsyncSession) -> None:
    session['my_key'] = 'value'
    await session.update_session()

Multiple sessions using multiple cookies can be configured in parallel. If this is the case, the session_arg_param_name argument can be used to customize the name of the session parameter being injected.

another_session_store = configure_async_sessions(
    app,
    redis,
    cookie_name="another_session_cookie",
    session_arg_param_name="another_session",
)

async def a_different_handler(another_session: AsyncSession) -> None:
    session['my_key'] = 'value'
    await another_session.update_session()

uapi.login#

The uapi.login addon enables login/logout for uapi apps.

The login addon requires a configured session store. Then, assuming the user IDs are ints, apply the addon and store the login_manager somewhere:

from uapi.login import configure_async_login

login_manager = configure_async_login(app, int, session_store)

You’ll need a login endpoint:

from uapi.login import AsyncLoginSession

async def login(login_session: AsyncLoginSession) -> Ok[None]:
    if login_session.user_id is not None:
        # Looks like this session is already associated with a user.
        return Ok(None)

    # Check credentials, like a password or token
    return Ok(None, await login_session.login_and_return(user_id))

Now your app’s handlers can declare the current_user_id parameter for dependency injection:

async def requires_logged_in_user(current_user_id: int) -> None:
    pass

An unauthenticated request will be denied with a Forbidden response.

A user can be logged out using AsyncLoginManager.logout().

async def logout_user() -> None:
    await login_manager.logout(user_id)

Security

The Redis Async session store uses cookies with the SameSite attribute set to lax by default, providing a degree of protection against cross-site request forgery when using forms.

Extra care should be provided to the login endpoint.