# Personal access tokens

Users create, list and revoke their own API tokens, like on GitHub.

```python
import os
from typing import Annotated

from fastapi import FastAPI, Security

from fastapi_multiauth import (
    APIKeyCookieAuth,
    HTTPBearerAuth,
    UnauthorizedError,
    checksum_token_generator,
    hash_token,
    verify_token_checksum,
)

SECRET_KEY = os.environ["SECRET_KEY"]
TOKENS: dict[int, dict] = {}


async def validate_session(username: str) -> str:
    return username


async def validate_api_token(token: str) -> str:
    if not verify_token_checksum(token, prefix="user_"):
        raise UnauthorizedError()  # a typo: no lookup needed
    for row in TOKENS.values():
        if row["hash"] == hash_token(token):
            return row["owner"]
    raise UnauthorizedError()


session = APIKeyCookieAuth(
    name="__Host-session", validator=validate_session, secret_key=SECRET_KEY
)
api_tokens = HTTPBearerAuth(
    validator=validate_api_token,
    prefix="user_",
    token_generator=checksum_token_generator,
)
SessionUser = Annotated[str, Security(session)]
FreshSessionUser = Annotated[str, Security(session.fresh(max_age=600))]

app = FastAPI()


@app.post("/tokens")
async def create_token(name: str, owner: FreshSessionUser):
    token = api_tokens.generate_token()
    token_id = len(TOKENS) + 1
    TOKENS[token_id] = {"name": name, "owner": owner, "hash": hash_token(token)}
    return {"id": token_id, "token": token}  # shown once, never again


@app.get("/tokens")
async def list_tokens(owner: SessionUser):
    return [
        {"id": token_id, "name": row["name"]}
        for token_id, row in TOKENS.items()
        if row["owner"] == owner
    ]


@app.delete("/tokens/{token_id}")
async def revoke_token(token_id: int, owner: SessionUser):
    if TOKENS.get(token_id, {}).get("owner") == owner:
        del TOKENS[token_id]
    return {"ok": True}


@app.get("/whoami")
async def whoami(owner: str = Security(api_tokens)):
    return {"owner": owner}
```

- Managing tokens requires the **session**, so a leaked token cannot create more tokens. Creating one also needs a [recent login](<https://fastapi-multiauth.d3vyce.fr/guide/step-up/index.md>).
- Tokens carry a checksum, so typos are refused before any lookup.
- The token is shown once; only its hash is kept.
