Skip to content

Personal access tokens

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

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.
  • Tokens carry a checksum, so typos are refused before any lookup.
  • The token is shown once; only its hash is kept.