Metadata-Version: 2.4
Name: fastapi-anchor
Version: 0.1.0
Summary: Session-based auth for FastAPI: cookie + DB storage, real logout
Home-page: https://github.com/andreahlert/fastapi-anchor
Author: Andre Ahlert
License: MIT
Project-URL: Homepage, https://github.com/andreahlert/fastapi-anchor
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: fastapi>=0.100
Requires-Dist: itsdangerous>=2.0
Requires-Dist: pydantic>=2.0
Provides-Extra: postgres
Requires-Dist: sqlalchemy[asyncio]>=2.0; extra == "postgres"
Requires-Dist: asyncpg; extra == "postgres"
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-asyncio; extra == "dev"
Requires-Dist: uvicorn; extra == "dev"
Requires-Dist: black; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Dynamic: home-page
Dynamic: license-file
Dynamic: requires-python

# FastAPI Anchor

Session-based auth for FastAPI: cookie (HttpOnly) + session stored in DB. **Real logout**: invalidate on the server so the session stops working immediately.

Inspired by [Lucia](https://lucia-auth.com) (JS/TS). Based on [fastapi-sessions](https://github.com/jordanisaacs/fastapi-sessions) by Jordan Isaacs.

## Features

- **Cookie-based session**: session id in HttpOnly cookie (signed). No token in JS.
- **Backend storage**: in-memory (dev) or **PostgreSQL** (production). Session in DB = you can invalidate on logout.
- **Real logout**: delete the session row; the next request with that cookie gets 401.
- Same ideas as the original fastapi-sessions: abstract `SessionFrontend` (cookie), `SessionBackend` (storage), `SessionVerifier` (dependency that returns session data).

## Installation

```bash
pip install fastapi-anchor
```

For PostgreSQL backend:

```bash
pip install fastapi-anchor[postgres]
```

## Quick start (in-memory)

```python
from uuid import UUID, uuid4
from fastapi import Depends, FastAPI, HTTPException, Response
from fastapi_anchor.backends.implementations import InMemoryBackend
from fastapi_anchor.frontends.implementations import SessionCookie, CookieParameters
from fastapi_anchor.session_verifier import SessionVerifier
from pydantic import BaseModel

class SessionData(BaseModel):
    user_id: str

cookie_params = CookieParameters(max_age=14*24*3600, httponly=True, secure=False)
cookie = SessionCookie(cookie_name="session", identifier="auth", auto_error=True, secret_key="your-secret", cookie_params=cookie_params)
backend = InMemoryBackend[UUID, SessionData]()

class AuthVerifier(SessionVerifier[UUID, SessionData]):
    def __init__(self):
        self._identifier = "auth"
        self._backend = backend
        self._auto_error = True
        self._auth_http_exception = HTTPException(status_code=401, detail="Invalid session")
    @property
    def identifier(self): return self._identifier
    @property
    def backend(self): return self._backend
    @property
    def auto_error(self): return self._auto_error
    @property
    def auth_http_exception(self): return self._auth_http_exception
    def verify_session(self, model: SessionData): return True

verifier = AuthVerifier()
app = FastAPI()

@app.post("/login")
async def login(user_id: str, response: Response):
    session_id = uuid4()
    await backend.create(session_id, SessionData(user_id=user_id))
    cookie.attach_to_response(response, session_id)
    return {"ok": True}

@app.get("/me", dependencies=[Depends(cookie)])
async def me(session_data: SessionData = Depends(verifier)):
    return session_data

@app.post("/logout")
async def logout(response: Response, session_id: UUID = Depends(cookie)):
    await backend.delete(session_id)
    cookie.delete_from_response(response)
    return {"ok": True}
```

## PostgreSQL backend (real logout)

Create the table and use `PostgresBackend`:

```python
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker
from fastapi_anchor.backends.implementations import PostgresBackend, anchor_sessions_table, PostgresSessionData
from sqlalchemy import MetaData

engine = create_async_engine("postgresql+asyncpg://user:pass@localhost/db")
async_session_factory = async_sessionmaker(engine, class_=AsyncSession, expire_on_commit=False)
metadata = MetaData()
sessions_table = anchor_sessions_table(metadata)
# Create tables: await engine.run_sync(metadata.create_all)

backend = PostgresBackend(async_session_factory, table=sessions_table)
# Session data for Postgres: user_id, expires_at, created_at (use PostgresSessionData or your own with those fields)
```

On logout, call `await backend.delete(session_id)` and clear the cookie; the session is removed from the DB and won't be valid anymore.

## License

MIT. Original fastapi-sessions by Jordan Isaacs (MIT).
